From 35f4edddcff85b55e191857407c7873c60174f8e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 09:41:40 -0500 Subject: [PATCH 001/270] [deps] Platform: Update @types/node to v22.10.2 (#12544) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 469f8bd1da1..4bf939c8b41 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -18,7 +18,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -106,9 +106,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index e32b9e8e987..6ab267be4a8 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -23,7 +23,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/package-lock.json b/package-lock.json index a8ae6daf236..6112ddbd966 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,7 +111,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -10072,9 +10072,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 03991f9d70b..5522f9c5be5 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", From ef70f7ddced2f7ae064090b94273d3c1d5ef09b1 Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Tue, 24 Dec 2024 12:03:04 -0500 Subject: [PATCH 002/270] [PM-4949] Update Accept Organization Invite Flow (#12202) * fix(accept-organization): Update accept organization invite flow Removed old accept organization flow and updated stylings to tailwind. --- .../accept-organization.component.html | 41 ++----------------- .../accept-organization.component.ts | 10 +---- 2 files changed, 6 insertions(+), 45 deletions(-) diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.html b/apps/web/src/app/auth/organization-invite/accept-organization.component.html index 88eaa37e8d2..cc08b840c30 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.html +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.html @@ -1,9 +1,9 @@ -
+
- -

+ +

@@ -11,36 +11,3 @@

-
-
-
-

{{ "joinOrganization" | i18n }}

-
-
-

- {{ orgName$ | async }} - {{ email }} -

-

{{ "joinOrganizationDesc" | i18n }}

-
- -
-
-
-
-
diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index f0425210b3d..660b2759988 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -56,20 +56,14 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { async unauthedHandler(qParams: Params): Promise { const invite = OrganizationInvite.fromParams(qParams); await this.acceptOrganizationInviteService.setOrganizationInvitation(invite); - await this.accelerateInviteAcceptIfPossible(invite); + await this.navigateInviteAcceptance(invite); } /** * In certain scenarios, we want to accelerate the user through the accept org invite process * For example, if the user has a BW account already, we want them to be taken to login instead of creation. */ - private async accelerateInviteAcceptIfPossible(invite: OrganizationInvite): Promise { - // if orgUserHasExistingUser is null, we can't determine the user's status - // so we don't want to accelerate the process - if (invite.orgUserHasExistingUser == null) { - return; - } - + private async navigateInviteAcceptance(invite: OrganizationInvite): Promise { // if user exists, send user to login if (invite.orgUserHasExistingUser) { await this.router.navigate(["/login"], { From eb7b2cd3e0b4f6689986340070066379903ee897 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Thu, 26 Dec 2024 11:16:57 -0500 Subject: [PATCH 003/270] PM-15593 Standard Login UI is shown when item has no Auth key added (#12432) * PM-15593 - Added getFilteredCiphersForTotpField method - Implemented getFilteredCiphersForTotpField on update list method - Updated tests to not always have a top field * account for no totp code ciphers in totp fields --- .../autofill-inline-menu-list.spec.ts.snap | 552 ++---------------- .../list/autofill-inline-menu-list.spec.ts | 52 +- .../pages/list/autofill-inline-menu-list.ts | 27 +- .../src/autofill/spec/autofill-mocks.ts | 2 +- 4 files changed, 93 insertions(+), 540 deletions(-) diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap index 3b8458ec2ab..acd06fb8c65 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap @@ -681,6 +681,7 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f class="cipher-container" > - -
- -
  • -
    - - -
    -
  • -
  • -
    - - -
    -
  • -
  • -
    - - -
    -
  • -
  • -
    - -
    -
  • -
  • -
    - -
    {{ startToTrayDescText }} -
    +
    -

    - {{ "premiumMembership" | i18n }} -

    -
    - -
    -
    - -

    {{ "premiumNotCurrentMember" | i18n }}

    -

    {{ "premiumSignUpAndGet" | i18n }}

    -
      -
    • - - {{ "ppremiumSignUpStorage" | i18n }} -
    • -
    • - - {{ "premiumSignUpTwoStepOptions" | i18n }} -
    • -
    • - - {{ "ppremiumSignUpReports" | i18n }} -
    • -
    • - - {{ "ppremiumSignUpTotp" | i18n }} -
    • -
    • - - {{ "ppremiumSignUpSupport" | i18n }} -
    • -
    • - - {{ "ppremiumSignUpFuture" | i18n }} -
    • -
    -

    {{ priceString }}

    - - -
    - -

    {{ "premiumCurrentMember" | i18n }}

    -

    {{ "premiumCurrentMemberThanks" | i18n }}

    - -
    -
    -
    diff --git a/apps/browser/src/billing/popup/settings/premium.component.ts b/apps/browser/src/billing/popup/settings/premium.component.ts deleted file mode 100644 index 70c8667646c..00000000000 --- a/apps/browser/src/billing/popup/settings/premium.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CurrencyPipe, Location } from "@angular/common"; -import { Component } from "@angular/core"; - -import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.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 { DialogService } from "@bitwarden/components"; - -@Component({ - selector: "app-premium", - templateUrl: "premium.component.html", -}) -export class PremiumComponent extends BasePremiumComponent { - priceString: string; - - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - apiService: ApiService, - configService: ConfigService, - logService: LogService, - private location: Location, - private currencyPipe: CurrencyPipe, - dialogService: DialogService, - environmentService: EnvironmentService, - billingAccountProfileStateService: BillingAccountProfileStateService, - ) { - super( - i18nService, - platformUtilsService, - apiService, - configService, - logService, - dialogService, - environmentService, - billingAccountProfileStateService, - ); - - // Support old price string. Can be removed in future once all translations are properly updated. - const thePrice = this.currencyPipe.transform(this.price, "$"); - // Safari extension crashes due to $1 appearing in the price string ($10.00). Escape the $ to fix. - const formattedPrice = this.platformUtilsService.isSafari() - ? thePrice.replace("$", "$$$") - : thePrice; - this.priceString = i18nService.t("premiumPrice", formattedPrice); - if (this.priceString.indexOf("%price%") > -1) { - this.priceString = this.priceString.replace("%price%", thePrice); - } - } - - goBack() { - this.location.back(); - } -} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 0a6ff456938..67d51a93cde 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -81,7 +81,6 @@ import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-do import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; -import { PremiumComponent } from "../billing/popup/settings/premium.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component"; @@ -395,12 +394,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, }), - ...extensionRefreshSwap(PremiumComponent, PremiumV2Component, { + { path: "premium", - component: PremiumComponent, + component: PremiumV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, ...extensionRefreshSwap(AppearanceComponent, AppearanceV2Component, { path: "appearance", canActivate: [authGuard], diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index b547078084b..04d681812fe 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -48,7 +48,6 @@ import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded- import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; -import { PremiumComponent } from "../billing/popup/settings/premium.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; import { HeaderComponent } from "../platform/popup/header.component"; import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component"; @@ -155,7 +154,6 @@ import "../platform/popup/locales"; NotificationsSettingsV1Component, AppearanceComponent, PasswordHistoryComponent, - PremiumComponent, RegisterComponent, SetPasswordComponent, VaultSettingsComponent, From 6539f06654e4596d06c63c85dcd5c5fa04ee290e Mon Sep 17 00:00:00 2001 From: Victoria League Date: Mon, 30 Dec 2024 14:46:29 -0500 Subject: [PATCH 021/270] [CL-135] Fix import statement for standalone component (#12589) --- libs/components/src/badge-list/badge-list.stories.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/components/src/badge-list/badge-list.stories.ts b/libs/components/src/badge-list/badge-list.stories.ts index 24024cb6bbd..ede005f6fd6 100644 --- a/libs/components/src/badge-list/badge-list.stories.ts +++ b/libs/components/src/badge-list/badge-list.stories.ts @@ -13,8 +13,7 @@ export default { component: BadgeListComponent, decorators: [ moduleMetadata({ - imports: [SharedModule, BadgeModule], - declarations: [BadgeListComponent], + imports: [SharedModule, BadgeModule, BadgeListComponent], providers: [ { provide: I18nService, From a95427eee0265c30322afec4c48e489a71ad1d7c Mon Sep 17 00:00:00 2001 From: Victoria League Date: Mon, 30 Dec 2024 16:42:53 -0500 Subject: [PATCH 022/270] [CL-524] Ignore flaky elements in Chromatic tests (#12561) --- .../src/platform/popup/layout/popup-layout.stories.ts | 10 ++++++++-- libs/components/src/menu/menu-trigger-for.directive.ts | 2 +- .../dialog-virtual-scroll-block.component.ts | 4 ++-- .../src/stories/kitchen-sink/kitchen-sink.stories.ts | 3 +++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index cd467cecbd2..c1ac8823261 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -41,7 +41,7 @@ class ExtensionContainerComponent {} @Component({ selector: "vault-placeholder", - template: ` + template: /*html*/ ` @@ -53,7 +53,7 @@ class ExtensionContainerComponent {} - + @@ -301,6 +301,12 @@ class MockVaultSubpageComponent {} export default { title: "Browser/Popup Layout", component: PopupPageComponent, + parameters: { + chromatic: { + // Disable tests while we troubleshoot their flaky-ness + disableSnapshot: true, + }, + }, decorators: [ moduleMetadata({ imports: [ diff --git a/libs/components/src/menu/menu-trigger-for.directive.ts b/libs/components/src/menu/menu-trigger-for.directive.ts index 786554e981c..96d430c5e6a 100644 --- a/libs/components/src/menu/menu-trigger-for.directive.ts +++ b/libs/components/src/menu/menu-trigger-for.directive.ts @@ -36,7 +36,7 @@ export class MenuTriggerForDirective implements OnDestroy { private defaultMenuConfig: OverlayConfig = { panelClass: "bit-menu-panel", hasBackdrop: true, - backdropClass: "cdk-overlay-transparent-backdrop", + backdropClass: ["cdk-overlay-transparent-backdrop", "bit-menu-panel-backdrop"], scrollStrategy: this.overlay.scrollStrategies.reposition(), positionStrategy: this.overlay .position() diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index a867d9cdf53..02b49a3e915 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -10,9 +10,9 @@ import { TableDataSource, TableModule } from "../../../table"; selector: "dialog-virtual-scroll-block", standalone: true, imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], - template: ` + template: /*html*/ ` - + Id diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index 203c510f814..44080e29049 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -130,6 +130,9 @@ export const MenuOpen: Story = { const menuButton = getAllByRole(table, "button")[0]; await userEvent.click(menuButton); }, + parameters: { + chromatic: { ignoreSelectors: [".bit-menu-panel-backdrop"] }, + }, }; export const DefaultDialogOpen: Story = { From 894dd2c89669acb018aaf6caecdc65b9cfda22a2 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Mon, 30 Dec 2024 19:24:31 -0500 Subject: [PATCH 023/270] [PM-16507] update new device verification notice state definition (#12608) --- libs/common/src/platform/state/state-definitions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 1ae5b080360..ce9eb5a15c0 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -180,5 +180,8 @@ export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOn export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition( "newDeviceVerificationNotice", "disk", + { + web: "disk-local", + }, ); export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); From 03d09578144b9b362f698fbd2825135a878ecefe Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 31 Dec 2024 14:56:23 +0100 Subject: [PATCH 024/270] Fix infinite password prompt loop on ssh-key import (#12569) --- apps/desktop/src/vault/app/vault/add-edit.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index b4f62a1254c..a798e61aa88 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -208,6 +208,9 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On }); } else { password = await this.getSshKeyPassword(); + if (password === "") { + return; + } await this.importSshKeyFromClipboard(password); } return; From 899b16966af90878bf9628f9b41a6c4d16cf727a Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:06:45 +0100 Subject: [PATCH 025/270] [PM-15814]Alert owners of reseller-managed orgs to renewal events (#12607) * Changes for the reseller alert * Resolve the null error * Refactor the reseller service * Fix the a failing test due to null date * Fix the No overload matches error * Resolve the null error * Resolve the null error * Resolve the null error * Change the date format * Remove unwanted comment * Refactor changes * Add the feature flag --- .../services/reseller-warning.service.ts | 142 ++++++++++++++++++ .../app/vault/org-vault/vault.component.html | 12 ++ .../app/vault/org-vault/vault.component.ts | 21 +++ apps/web/src/locales/en/messages.json | 43 ++++++ .../organization-billing-metadata.response.ts | 13 ++ libs/common/src/enums/feature-flag.enum.ts | 2 + 6 files changed, 233 insertions(+) create mode 100644 apps/web/src/app/billing/services/reseller-warning.service.ts diff --git a/apps/web/src/app/billing/services/reseller-warning.service.ts b/apps/web/src/app/billing/services/reseller-warning.service.ts new file mode 100644 index 00000000000..bfd5be3233a --- /dev/null +++ b/apps/web/src/app/billing/services/reseller-warning.service.ts @@ -0,0 +1,142 @@ +import { Injectable } from "@angular/core"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +export interface ResellerWarning { + type: "info" | "warning"; + message: string; +} + +@Injectable({ providedIn: "root" }) +export class ResellerWarningService { + private readonly RENEWAL_WARNING_DAYS = 14; + private readonly GRACE_PERIOD_DAYS = 30; + + constructor(private i18nService: I18nService) {} + + getWarning( + organization: Organization, + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): ResellerWarning | null { + if (!organization.hasReseller) { + return null; // If no reseller, return null immediately + } + + // Check for past due warning first (highest priority) + if (this.shouldShowPastDueWarning(organizationBillingMetadata)) { + const gracePeriodEnd = this.getGracePeriodEndDate(organizationBillingMetadata.invoiceDueDate); + if (!gracePeriodEnd) { + return null; + } + return { + type: "warning", + message: this.i18nService.t( + "resellerPastDueWarning", + organization.providerName, + this.formatDate(gracePeriodEnd), + ), + } as ResellerWarning; + } + + // Check for open invoice warning + if (this.shouldShowInvoiceWarning(organizationBillingMetadata)) { + const invoiceCreatedDate = organizationBillingMetadata.invoiceCreatedDate; + const invoiceDueDate = organizationBillingMetadata.invoiceDueDate; + if (!invoiceCreatedDate || !invoiceDueDate) { + return null; + } + return { + type: "info", + message: this.i18nService.t( + "resellerOpenInvoiceWarning", + organization.providerName, + this.formatDate(organizationBillingMetadata.invoiceCreatedDate), + this.formatDate(organizationBillingMetadata.invoiceDueDate), + ), + } as ResellerWarning; + } + + // Check for renewal warning + if (this.shouldShowRenewalWarning(organizationBillingMetadata)) { + const subPeriodEndDate = organizationBillingMetadata.subPeriodEndDate; + if (!subPeriodEndDate) { + return null; + } + + return { + type: "info", + message: this.i18nService.t( + "resellerRenewalWarning", + organization.providerName, + this.formatDate(organizationBillingMetadata.subPeriodEndDate), + ), + } as ResellerWarning; + } + + return null; + } + + private shouldShowRenewalWarning( + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): boolean { + if ( + !organizationBillingMetadata.hasSubscription || + !organizationBillingMetadata.subPeriodEndDate + ) { + return false; + } + const renewalDate = new Date(organizationBillingMetadata.subPeriodEndDate); + const daysUntilRenewal = Math.ceil( + (renewalDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24), + ); + return daysUntilRenewal <= this.RENEWAL_WARNING_DAYS; + } + + private shouldShowInvoiceWarning( + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): boolean { + if ( + !organizationBillingMetadata.hasOpenInvoice || + !organizationBillingMetadata.invoiceDueDate + ) { + return false; + } + const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate); + return invoiceDueDate > new Date(); + } + + private shouldShowPastDueWarning( + organizationBillingMetadata: OrganizationBillingMetadataResponse, + ): boolean { + if ( + !organizationBillingMetadata.hasOpenInvoice || + !organizationBillingMetadata.invoiceDueDate + ) { + return false; + } + const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate); + return invoiceDueDate <= new Date() && !organizationBillingMetadata.isSubscriptionUnpaid; + } + + private getGracePeriodEndDate(dueDate: Date | null): Date | null { + if (!dueDate) { + return null; + } + const gracePeriodEnd = new Date(dueDate); + gracePeriodEnd.setDate(gracePeriodEnd.getDate() + this.GRACE_PERIOD_DAYS); + return gracePeriodEnd; + } + + private formatDate(date: Date | null): string { + if (!date) { + return "N/A"; + } + return new Date(date).toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + }); + } +} diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/vault/org-vault/vault.component.html index 52f3ea026ff..512f97144de 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -19,6 +19,18 @@ + + + {{ resellerWarning?.message }} + + (false); protected currentSearchText$: Observable; protected freeTrial$: Observable; + protected resellerWarning$: Observable; /** * A list of collections that the user can assign items to and edit those items within. * @protected @@ -203,6 +208,7 @@ export class VaultComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); protected addAccessStatus$ = new BehaviorSubject(0); private extensionRefreshEnabled: boolean; + private resellerManagedOrgAlert: boolean; private vaultItemDialogRef?: DialogRef | undefined; private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( @@ -259,6 +265,7 @@ export class VaultComponent implements OnInit, OnDestroy { private trialFlowService: TrialFlowService, protected billingApiService: BillingApiServiceAbstraction, private organizationBillingService: OrganizationBillingServiceAbstraction, + private resellerWarningService: ResellerWarningService, ) {} async ngOnInit() { @@ -266,6 +273,10 @@ export class VaultComponent implements OnInit, OnDestroy { FeatureFlag.ExtensionRefresh, ); + this.resellerManagedOrgAlert = await this.configService.getFeatureFlag( + FeatureFlag.ResellerManagedOrgAlert, + ); + this.trashCleanupWarning = this.i18nService.t( this.platformUtilsService.isSelfHost() ? "trashCleanupWarningSelfHosted" @@ -612,6 +623,16 @@ export class VaultComponent implements OnInit, OnDestroy { }), ); + this.resellerWarning$ = organization$.pipe( + filter((org) => org.isOwner && this.resellerManagedOrgAlert), + switchMap((org) => + from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe( + map((metadata) => ({ org, metadata })), + ), + ), + map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)), + ); + firstSetup$ .pipe( switchMap(() => this.refresh$), diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index acbb348048c..7c338fc6e97 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10011,5 +10011,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts index d9733aa80f2..c5023cb64c1 100644 --- a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts +++ b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts @@ -6,6 +6,10 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { isOnSecretsManagerStandalone: boolean; isSubscriptionUnpaid: boolean; hasSubscription: boolean; + hasOpenInvoice: boolean; + invoiceDueDate: Date | null; + invoiceCreatedDate: Date | null; + subPeriodEndDate: Date | null; constructor(response: any) { super(response); @@ -14,5 +18,14 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { this.isOnSecretsManagerStandalone = this.getResponseProperty("IsOnSecretsManagerStandalone"); this.isSubscriptionUnpaid = this.getResponseProperty("IsSubscriptionUnpaid"); this.hasSubscription = this.getResponseProperty("HasSubscription"); + this.hasOpenInvoice = this.getResponseProperty("HasOpenInvoice"); + + this.invoiceDueDate = this.parseDate(this.getResponseProperty("InvoiceDueDate")); + this.invoiceCreatedDate = this.parseDate(this.getResponseProperty("InvoiceCreatedDate")); + this.subPeriodEndDate = this.parseDate(this.getResponseProperty("SubPeriodEndDate")); + } + + private parseDate(dateString: any): Date | null { + return dateString ? new Date(dateString) : null; } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index cc2abed3ba1..135119bf133 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -43,6 +43,7 @@ export enum FeatureFlag { PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", PrivateKeyRegeneration = "pm-12241-private-key-regeneration", + ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -96,6 +97,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, [FeatureFlag.PM12443RemovePagingLogic]: FALSE, [FeatureFlag.PrivateKeyRegeneration]: FALSE, + [FeatureFlag.ResellerManagedOrgAlert]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; From e1778f4282f92fba7e0abd00081b4b20c7161641 Mon Sep 17 00:00:00 2001 From: Brant DeBow <125889545+brant-livefront@users.noreply.github.com> Date: Tue, 31 Dec 2024 15:16:31 -0500 Subject: [PATCH 026/270] [PM-16530] [BRE-283] Changes to support hardening on the Mac desktop app (#12632) * [DEVOPS-1424] Changes to support hardening on the Mac desktop app * Remove unsigned memory exception * Remove exceptions from the local (non-MAS) mac builds as well --------- Co-authored-by: Matt Bishop --- apps/desktop/electron-builder.json | 2 +- .../resources/entitlements.desktop_proxy.inherit.plist | 2 ++ apps/desktop/resources/entitlements.desktop_proxy.plist | 2 ++ apps/desktop/resources/entitlements.mac.plist | 4 ---- apps/desktop/resources/entitlements.mas.inherit.plist | 4 +--- apps/desktop/resources/entitlements.mas.plist | 2 ++ 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 898ad086b29..c8114d947e4 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -133,7 +133,7 @@ "entitlements": "resources/entitlements.mas.plist", "entitlementsInherit": "resources/entitlements.mas.inherit.plist", "entitlementsLoginHelper": "resources/entitlements.mas.loginhelper.plist", - "hardenedRuntime": false, + "hardenedRuntime": true, "extendInfo": { "LSMinimumSystemVersion": "12", "ElectronTeamID": "LTZ2PFU5D6" diff --git a/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist b/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist index 794eada1cad..fca5f02d52d 100644 --- a/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist +++ b/apps/desktop/resources/entitlements.desktop_proxy.inherit.plist @@ -6,5 +6,7 @@ com.apple.security.inherit + com.apple.security.cs.allow-jit + diff --git a/apps/desktop/resources/entitlements.desktop_proxy.plist b/apps/desktop/resources/entitlements.desktop_proxy.plist index d5c7b8a2cc8..1a39a482389 100644 --- a/apps/desktop/resources/entitlements.desktop_proxy.plist +++ b/apps/desktop/resources/entitlements.desktop_proxy.plist @@ -8,5 +8,7 @@ LTZ2PFU5D6.com.bitwarden.desktop + com.apple.security.cs.allow-jit + diff --git a/apps/desktop/resources/entitlements.mac.plist b/apps/desktop/resources/entitlements.mac.plist index 34c561bd03f..e273bcc7eca 100644 --- a/apps/desktop/resources/entitlements.mac.plist +++ b/apps/desktop/resources/entitlements.mac.plist @@ -4,10 +4,6 @@ com.apple.security.cs.allow-jit - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-library-validation - {{ "estimatedTax" | i18n }}: {{ estimatedTax | currency: "USD $" }}
    diff --git a/apps/web/src/app/billing/individual/premium/premium-v2.component.ts b/apps/web/src/app/billing/individual/premium/premium-v2.component.ts index 72dc5a30d5f..2abab57b7e0 100644 --- a/apps/web/src/app/billing/individual/premium/premium-v2.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium-v2.component.ts @@ -5,10 +5,13 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { combineLatest, concatMap, from, Observable, of } from "rxjs"; +import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -44,6 +47,7 @@ export class PremiumV2Component { FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader, ); + protected estimatedTax: number = 0; protected readonly familyPlanMaxUserCount = 6; protected readonly premiumPrice = 10; protected readonly storageGBPrice = 4; @@ -60,6 +64,7 @@ export class PremiumV2Component { private syncService: SyncService, private toastService: ToastService, private tokenService: TokenService, + private taxService: TaxServiceAbstraction, ) { this.isSelfHost = this.platformUtilsService.isSelfHost(); @@ -82,6 +87,12 @@ export class PremiumV2Component { }), ) .subscribe(); + + this.addOnFormGroup.controls.additionalStorage.valueChanges + .pipe(debounceTime(1000), takeUntilDestroyed()) + .subscribe(() => { + this.refreshSalesTax(); + }); } finalizeUpgrade = async () => { @@ -158,12 +169,6 @@ export class PremiumV2Component { return this.storageGBPrice * this.addOnFormGroup.value.additionalStorage; } - protected get estimatedTax(): number { - return this.taxInfoComponent?.taxRate != null - ? (this.taxInfoComponent.taxRate / 100) * this.subtotal - : 0; - } - protected get premiumURL(): string { return `${this.cloudWebVaultURL}/#/settings/subscription/premium`; } @@ -179,4 +184,36 @@ export class PremiumV2Component { protected async onLicenseFileSelectedChanged(): Promise { await this.postFinalizeUpgrade(); } + + private refreshSalesTax(): void { + if (!this.taxInfoComponent.country || !this.taxInfoComponent.postalCode) { + return; + } + const request: PreviewIndividualInvoiceRequest = { + passwordManager: { + additionalStorage: this.addOnFormGroup.value.additionalStorage, + }, + taxInformation: { + postalCode: this.taxInfoComponent.postalCode, + country: this.taxInfoComponent.country, + }, + }; + + this.taxService + .previewIndividualInvoice(request) + .then((invoice) => { + this.estimatedTax = invoice.taxAmount; + }) + .catch((error) => { + this.toastService.showToast({ + title: "", + variant: "error", + message: this.i18nService.t(error.message), + }); + }); + } + + protected onTaxInformationChanged(): void { + this.refreshSalesTax(); + } } diff --git a/apps/web/src/app/billing/individual/premium/premium.component.html b/apps/web/src/app/billing/individual/premium/premium.component.html index 8b848b48dab..12b6932d0f5 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.html +++ b/apps/web/src/app/billing/individual/premium/premium.component.html @@ -122,7 +122,7 @@

    {{ "paymentInformation" | i18n }}

    - +
    {{ "planPrice" | i18n }}: {{ subtotal | currency: "USD $" }} diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/premium.component.ts index 9e2be6dcab1..76ca25c8cc6 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium.component.ts @@ -1,13 +1,17 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit, ViewChild } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; import { firstValueFrom, Observable } from "rxjs"; +import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -39,6 +43,9 @@ export class PremiumComponent implements OnInit { protected addonForm = new FormGroup({ additionalStorage: new FormControl(0, [Validators.max(99), Validators.min(0)]), }); + + private estimatedTax: number = 0; + constructor( private apiService: ApiService, private i18nService: I18nService, @@ -50,9 +57,16 @@ export class PremiumComponent implements OnInit { private environmentService: EnvironmentService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, + private taxService: TaxServiceAbstraction, ) { this.selfHosted = platformUtilsService.isSelfHost(); this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + + this.addonForm.controls.additionalStorage.valueChanges + .pipe(debounceTime(1000), takeUntilDestroyed()) + .subscribe(() => { + this.refreshSalesTax(); + }); } protected setSelectedFile(event: Event) { const fileInputEl = event.target; @@ -154,12 +168,42 @@ export class PremiumComponent implements OnInit { } get taxCharges(): number { - return this.taxInfoComponent != null && this.taxInfoComponent.taxRate != null - ? (this.taxInfoComponent.taxRate / 100) * this.subtotal - : 0; + return this.estimatedTax; } get total(): number { return this.subtotal + this.taxCharges || 0; } + + private refreshSalesTax(): void { + if (!this.taxInfoComponent.country || !this.taxInfoComponent.postalCode) { + return; + } + const request: PreviewIndividualInvoiceRequest = { + passwordManager: { + additionalStorage: this.addonForm.value.additionalStorage, + }, + taxInformation: { + postalCode: this.taxInfoComponent.postalCode, + country: this.taxInfoComponent.country, + }, + }; + + this.taxService + .previewIndividualInvoice(request) + .then((invoice) => { + this.estimatedTax = invoice.taxAmount; + }) + .catch((error) => { + this.toastService.showToast({ + title: "", + variant: "error", + message: this.i18nService.t(error.message), + }); + }); + } + + protected onTaxInformationChanged(): void { + this.refreshSalesTax(); + } } diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 93751f0ef72..78005275f12 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -344,25 +344,14 @@

    - - - - + + + + +

    +
    + +

    + + {{ "estimatedTax" | i18n }} + + + {{ estimatedTax | currency: "USD" : "$" }} + +

    +
    +

    (); + protected taxInformation: TaxInformation; + constructor( @Inject(DIALOG_DATA) private dialogParams: ChangePlanDialogParams, private dialogRef: DialogRef, @@ -189,6 +195,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, + private taxService: TaxServiceAbstraction, ) {} async ngOnInit(): Promise { @@ -267,6 +274,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.setInitialPlanSelection(); this.loading = false; + + const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); + this.taxInformation = TaxInformation.from(taxInfo); + + this.refreshSalesTax(); } setInitialPlanSelection() { @@ -402,6 +414,12 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } this.selectedPlan = plan; this.formGroup.patchValue({ productTier: plan.productTier }); + + try { + this.refreshSalesTax(); + } catch { + this.estimatedTax = 0; + } } ngOnDestroy() { @@ -567,12 +585,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ); } - get taxCharges() { - return this.taxComponent != null && this.taxComponent.taxRate != null - ? (this.taxComponent.taxRate / 100) * this.passwordManagerSubtotal - : 0; - } - get passwordManagerSeats() { if (this.selectedPlan.productTier === ProductTierType.Families) { return this.selectedPlan.PasswordManager.baseSeats; @@ -584,15 +596,15 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { if (this.organization.useSecretsManager) { return ( this.passwordManagerSubtotal + - this.additionalStorageTotal(this.selectedPlan) + - this.secretsManagerSubtotal + - this.taxCharges || 0 + this.additionalStorageTotal(this.selectedPlan) + + this.secretsManagerSubtotal + + this.estimatedTax ); } return ( this.passwordManagerSubtotal + - this.additionalStorageTotal(this.selectedPlan) + - this.taxCharges || 0 + this.additionalStorageTotal(this.selectedPlan) + + this.estimatedTax ); } @@ -645,8 +657,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } changedCountry() { - if (this.deprecateStripeSourcesAPI && this.paymentV2Component && this.taxComponent) { - this.paymentV2Component.showBankAccount = this.taxComponent.country === "US"; + if (this.deprecateStripeSourcesAPI && this.paymentV2Component) { + this.paymentV2Component.showBankAccount = this.taxInformation.country === "US"; if ( !this.paymentV2Component.showBankAccount && @@ -654,8 +666,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ) { this.paymentV2Component.select(PaymentMethodType.Card); } - } else if (this.paymentComponent && this.taxComponent) { - this.paymentComponent!.hideBank = this.taxComponent?.taxFormGroup?.value.country !== "US"; + } else if (this.paymentComponent && this.taxInformation) { + this.paymentComponent!.hideBank = this.taxInformation.country !== "US"; // Bank Account payments are only available for US customers if ( this.paymentComponent.hideBank && @@ -667,9 +679,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } } + protected taxInformationChanged(event: TaxInformation): void { + this.taxInformation = event; + this.changedCountry(); + this.refreshSalesTax(); + } + submit = async () => { - if (!this.taxComponent?.taxFormGroup.valid && this.taxComponent?.taxFormGroup.touched) { - this.taxComponent?.taxFormGroup.markAllAsTouched(); + if (this.taxComponent !== undefined && !this.taxComponent.validate()) { return; } @@ -723,8 +740,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; if (this.showPayment) { - request.billingAddressCountry = this.taxComponent.taxFormGroup?.value.country; - request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode; + request.billingAddressCountry = this.taxInformation.country; + request.billingAddressPostalCode = this.taxInformation.postalCode; } // Secrets Manager @@ -735,15 +752,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const tokenizedPaymentSource = await this.paymentV2Component.tokenize(); const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); updatePaymentMethodRequest.paymentSource = tokenizedPaymentSource; - updatePaymentMethodRequest.taxInformation = { - country: this.taxComponent.country, - postalCode: this.taxComponent.postalCode, - taxId: this.taxComponent.taxId, - line1: this.taxComponent.line1, - line2: this.taxComponent.line2, - city: this.taxComponent.city, - state: this.taxComponent.state, - }; + updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( + this.taxInformation, + ); await this.billingApiService.updateOrganizationPaymentMethod( this.organizationId, @@ -754,8 +765,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const paymentRequest = new PaymentRequest(); paymentRequest.paymentToken = tokenResult[0]; paymentRequest.paymentMethodType = tokenResult[1]; - paymentRequest.country = this.taxComponent.taxFormGroup?.value.country; - paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode; + paymentRequest.country = this.taxInformation.country; + paymentRequest.postalCode = this.taxInformation.postalCode; await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); } } @@ -944,4 +955,48 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { manageSelectableProduct(index: number) { return index; } + + private refreshSalesTax(): void { + if (!this.taxInformation.country || !this.taxInformation.postalCode) { + return; + } + + const request: PreviewOrganizationInvoiceRequest = { + organizationId: this.organizationId, + passwordManager: { + additionalStorage: 0, + plan: this.selectedPlan?.type, + seats: this.sub.seats, + }, + taxInformation: { + postalCode: this.taxInformation.postalCode, + country: this.taxInformation.country, + taxId: this.taxInformation.taxId, + }, + }; + + if (this.organization.useSecretsManager) { + request.secretsManager = { + seats: this.sub.smSeats, + additionalMachineAccounts: this.sub.smServiceAccounts, + }; + } + + this.taxService + .previewOrganizationInvoice(request) + .then((invoice) => { + this.estimatedTax = invoice.taxAmount; + }) + .catch((error) => { + this.toastService.showToast({ + title: "", + variant: "error", + message: this.i18nService.t(error.message), + }); + }); + } + + protected canUpdatePaymentInformation(): boolean { + return this.upgradeRequiresPaymentMethod || this.showPayment || this.isPaymentSourceEmpty(); + } } diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.html b/apps/web/src/app/billing/organizations/organization-plans.component.html index e1b74abea71..d37f95e3aa2 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.html +++ b/apps/web/src/app/billing/organizations/organization-plans.component.html @@ -335,7 +335,7 @@ >{{ "additionalUsers" | i18n }}: {{ "users" | i18n }}: - {{ formGroup.controls["additionalSeats"].value || 0 }} × + {{ formGroup.controls.additionalSeats.value || 0 }} × {{ (selectablePlan.isAnnual ? selectablePlan.PasswordManager.seatPrice / 12 @@ -355,7 +355,7 @@ *ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption" > {{ "additionalStorageGb" | i18n }}: - {{ formGroup.controls["additionalStorage"].value || 0 }} × + {{ formGroup.controls.additionalStorage.value || 0 }} × {{ (selectablePlan.isAnnual ? selectablePlan.PasswordManager.additionalStoragePricePerGb / 12 @@ -388,7 +388,7 @@ >{{ "additionalUsers" | i18n }}: {{ "users" | i18n }}: - {{ formGroup.controls["additionalSeats"].value || 0 }} × + {{ formGroup.controls.additionalSeats.value || 0 }} × {{ selectablePlan.PasswordManager.seatPrice | currency: "$" }} {{ "monthAbbr" | i18n }} = {{ @@ -403,7 +403,7 @@ *ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption" > {{ "additionalStorageGb" | i18n }}: - {{ formGroup.controls["additionalStorage"].value || 0 }} × + {{ formGroup.controls.additionalStorage.value || 0 }} × {{ selectablePlan.PasswordManager.additionalStoragePricePerGb | currency: "$" }} {{ "monthAbbr" | i18n }} = {{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "month" | i18n }} @@ -440,7 +440,12 @@ - +

    {{ "passwordManagerPlanPrice" | i18n }}: {{ passwordManagerSubtotal | currency: "USD $" }} @@ -450,7 +455,7 @@
    - {{ "estimatedTax" | i18n }}: {{ taxCharges | currency: "USD $" }} + {{ "estimatedTax" | i18n }}: {{ estimatedTax | currency: "USD $" }}

    diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index e7a011792ae..4592f8de894 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -12,7 +12,9 @@ import { import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; +import { debounceTime } from "rxjs/operators"; +import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -26,9 +28,12 @@ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/mode import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { TaxInformation } from "@bitwarden/common/billing/models/domain"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; +import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; @@ -50,7 +55,6 @@ import { OrganizationCreateModule } from "../../admin-console/organizations/crea import { BillingSharedModule, secretsManagerSubscribeFormFactory } from "../shared"; import { PaymentV2Component } from "../shared/payment/payment-v2.component"; import { PaymentComponent } from "../shared/payment/payment.component"; -import { TaxInfoComponent } from "../shared/tax-info.component"; interface OnSuccessArgs { organizationId: string; @@ -72,13 +76,14 @@ const Allowed2020PlansForLegacyProviders = [ export class OrganizationPlansComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @ViewChild(PaymentV2Component) paymentV2Component: PaymentV2Component; - @ViewChild(TaxInfoComponent) taxComponent: TaxInfoComponent; + @ViewChild(ManageTaxInformationComponent) taxComponent: ManageTaxInformationComponent; - @Input() organizationId: string; + @Input() organizationId?: string; @Input() showFree = true; @Input() showCancel = false; @Input() acceptingSponsorship = false; @Input() currentPlan: PlanResponse; + selectedFile: File; @Input() @@ -93,6 +98,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private _productTier = ProductTierType.Free; + protected taxInformation: TaxInformation; + @Input() get plan(): PlanType { return this._plan; @@ -149,7 +156,10 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { billing: BillingResponse; provider: ProviderResponse; - private destroy$ = new Subject(); + protected estimatedTax: number = 0; + protected total: number = 0; + + private destroy$: Subject = new Subject(); constructor( private apiService: ApiService, @@ -168,6 +178,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private toastService: ToastService, private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, + private taxService: TaxServiceAbstraction, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -181,6 +192,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.organization = await this.organizationService.get(this.organizationId); this.billing = await this.organizationApiService.getBilling(this.organizationId); this.sub = await this.organizationApiService.getSubscription(this.organizationId); + this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId); + } else { + this.taxInformation = await this.apiService.getTaxInfo(); } if (!this.selfHosted) { @@ -241,6 +255,16 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } this.loading = false; + + this.formGroup.valueChanges.pipe(debounceTime(1000), takeUntil(this.destroy$)).subscribe(() => { + this.refreshSalesTax(); + }); + + this.secretsManagerForm.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + this.refreshSalesTax(); + }); } ngOnDestroy() { @@ -438,17 +462,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { return this.selectedPlan.trialPeriodDays != null; } - get taxCharges() { - return this.taxComponent != null && this.taxComponent.taxRate != null - ? (this.taxComponent.taxRate / 100) * - (this.passwordManagerSubtotal + this.secretsManagerSubtotal) - : 0; - } - - get total() { - return this.passwordManagerSubtotal + this.secretsManagerSubtotal + this.taxCharges || 0; - } - get paymentDesc() { if (this.acceptingSponsorship) { return this.i18nService.t("paymentSponsored"); @@ -554,9 +567,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.changedProduct(); } - changedCountry() { + protected changedCountry(): void { if (this.deprecateStripeSourcesAPI) { - this.paymentV2Component.showBankAccount = this.taxComponent.country === "US"; + this.paymentV2Component.showBankAccount = this.taxInformation?.country === "US"; if ( !this.paymentV2Component.showBankAccount && this.paymentV2Component.selected === PaymentMethodType.BankAccount @@ -564,7 +577,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.paymentV2Component.select(PaymentMethodType.Card); } } else { - this.paymentComponent.hideBank = this.taxComponent.taxFormGroup?.value.country !== "US"; + this.paymentComponent.hideBank = this.taxInformation?.country !== "US"; if ( this.paymentComponent.hideBank && this.paymentComponent.method === PaymentMethodType.BankAccount @@ -575,28 +588,31 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } } - cancel() { + protected onTaxInformationChanged(event: TaxInformation): void { + this.taxInformation = event; + this.changedCountry(); + this.refreshSalesTax(); + } + + protected cancel(): void { this.onCanceled.emit(); } - setSelectedFile(event: Event) { + protected setSelectedFile(event: Event): void { const fileInputEl = event.target; this.selectedFile = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null; } submit = async () => { - if (this.taxComponent) { - if (!this.taxComponent?.taxFormGroup.valid) { - this.taxComponent?.taxFormGroup.markAllAsTouched(); - return; - } + if (this.taxComponent && !this.taxComponent.validate()) { + return; } if (this.singleOrgPolicyBlock) { return; } const doSubmit = async (): Promise => { - let orgId: string = null; + let orgId: string; if (this.createOrganization) { const orgKey = await this.keyService.makeOrgKey(); const key = orgKey[0].encryptedString; @@ -607,11 +623,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { const collectionCt = collection.encryptedString; const orgKeys = await this.keyService.makeKeyPair(orgKey[1]); - if (this.selfHosted) { - orgId = await this.createSelfHosted(key, collectionCt, orgKeys); - } else { - orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]); - } + orgId = this.selfHosted + ? await this.createSelfHosted(key, collectionCt, orgKeys) + : await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]); this.toastService.showToast({ variant: "success", @@ -619,7 +633,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { message: this.i18nService.t("organizationReadyToGo"), }); } else { - orgId = await this.updateOrganization(orgId); + orgId = await this.updateOrganization(); this.toastService.showToast({ variant: "success", title: null, @@ -653,7 +667,63 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.messagingService.send("organizationCreated", { organizationId }); }; - private async updateOrganization(orgId: string) { + protected get showTaxIdField(): boolean { + switch (this.formGroup.controls.productTier.value) { + case ProductTierType.Free: + case ProductTierType.Families: + return false; + default: + return true; + } + } + + private refreshSalesTax(): void { + if (this.formGroup.controls.plan.value == PlanType.Free) { + this.estimatedTax = 0; + return; + } + + if (!this.taxComponent.validate()) { + return; + } + + const request: PreviewOrganizationInvoiceRequest = { + organizationId: this.organizationId, + passwordManager: { + additionalStorage: this.formGroup.controls.additionalStorage.value, + plan: this.formGroup.controls.plan.value, + seats: this.formGroup.controls.additionalSeats.value, + }, + taxInformation: { + postalCode: this.taxInformation.postalCode, + country: this.taxInformation.country, + taxId: this.taxInformation.taxId, + }, + }; + + if (this.secretsManagerForm.controls.enabled.value === true) { + request.secretsManager = { + seats: this.secretsManagerForm.controls.userSeats.value, + additionalMachineAccounts: this.secretsManagerForm.controls.additionalServiceAccounts.value, + }; + } + + this.taxService + .previewOrganizationInvoice(request) + .then((invoice) => { + this.estimatedTax = invoice.taxAmount; + this.total = invoice.totalAmount; + }) + .catch((error) => { + this.toastService.showToast({ + title: "", + variant: "error", + message: this.i18nService.t(error.message), + }); + }); + } + + private async updateOrganization() { const request = new OrganizationUpgradeRequest(); request.additionalSeats = this.formGroup.controls.additionalSeats.value; request.additionalStorageGb = this.formGroup.controls.additionalStorage.value; @@ -661,8 +731,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; - request.billingAddressCountry = this.taxComponent.taxFormGroup?.value.country; - request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode; + request.billingAddressCountry = this.taxInformation?.country; + request.billingAddressPostalCode = this.taxInformation?.postalCode; // Secrets Manager this.buildSecretsManagerRequest(request); @@ -671,10 +741,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.deprecateStripeSourcesAPI) { const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); updatePaymentMethodRequest.paymentSource = await this.paymentV2Component.tokenize(); - const expandedTaxInfoUpdateRequest = new ExpandedTaxInfoUpdateRequest(); - expandedTaxInfoUpdateRequest.country = this.taxComponent.country; - expandedTaxInfoUpdateRequest.postalCode = this.taxComponent.postalCode; - updatePaymentMethodRequest.taxInformation = expandedTaxInfoUpdateRequest; + updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( + this.taxInformation, + ); await this.billingApiService.updateOrganizationPaymentMethod( this.organizationId, updatePaymentMethodRequest, @@ -684,8 +753,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { const paymentRequest = new PaymentRequest(); paymentRequest.paymentToken = paymentToken; paymentRequest.paymentMethodType = paymentMethodType; - paymentRequest.country = this.taxComponent.taxFormGroup?.value.country; - paymentRequest.postalCode = this.taxComponent.taxFormGroup?.value.postalCode; + paymentRequest.country = this.taxInformation?.country; + paymentRequest.postalCode = this.taxInformation?.postalCode; await this.organizationApiService.updatePayment(this.organizationId, paymentRequest); } } @@ -709,7 +778,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { collectionCt: string, orgKeys: [string, EncString], orgKey: SymmetricCryptoKey, - ) { + ): Promise { const request = new OrganizationCreateRequest(); request.key = key; request.collectionName = collectionCt; @@ -738,15 +807,13 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; - request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode; - request.billingAddressCountry = this.taxComponent.taxFormGroup?.value.country; - if (this.taxComponent.taxFormGroup?.value.includeTaxId) { - request.taxIdNumber = this.taxComponent.taxFormGroup?.value.taxId; - request.billingAddressLine1 = this.taxComponent.taxFormGroup?.value.line1; - request.billingAddressLine2 = this.taxComponent.taxFormGroup?.value.line2; - request.billingAddressCity = this.taxComponent.taxFormGroup?.value.city; - request.billingAddressState = this.taxComponent.taxFormGroup?.value.state; - } + request.billingAddressPostalCode = this.taxInformation?.postalCode; + request.billingAddressCountry = this.taxInformation?.country; + request.taxIdNumber = this.taxInformation?.taxId; + request.billingAddressLine1 = this.taxInformation?.line1; + request.billingAddressLine2 = this.taxInformation?.line2; + request.billingAddressCity = this.taxInformation?.city; + request.billingAddressState = this.taxInformation?.state; } // Secrets Manager diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html index 78f9955d31a..2c2dba938bc 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html @@ -63,20 +63,5 @@ {{ "paymentChargedWithUnpaidSubscription" | i18n }}

    - - -

    {{ "taxInformation" | i18n }}

    -

    {{ "taxInformationDesc" | i18n }}

    - - -
    diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index 4ed35461c72..270ba54f70d 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Location } from "@angular/common"; -import { Component, OnDestroy, ViewChild } from "@angular/core"; +import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; import { from, lastValueFrom, switchMap } from "rxjs"; @@ -11,7 +11,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; @@ -22,7 +21,6 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { FreeTrial } from "../../../core/types/free-trial"; import { TrialFlowService } from "../../services/trial-flow.service"; -import { TaxInfoComponent } from "../../shared"; import { AddCreditDialogResult, openAddCreditDialog, @@ -36,8 +34,6 @@ import { templateUrl: "./organization-payment-method.component.html", }) export class OrganizationPaymentMethodComponent implements OnDestroy { - @ViewChild(TaxInfoComponent) taxInfoComponent: TaxInfoComponent; - organizationId: string; isUnpaid = false; accountCredit: number; @@ -155,6 +151,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { data: { initialPaymentMethod: this.paymentSource?.type, organizationId: this.organizationId, + productTier: this.organization?.productTierType, }, }); @@ -170,6 +167,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { data: { initialPaymentMethod: this.paymentSource?.type, organizationId: this.organizationId, + productTier: this.organization?.productTierType, }, }); const result = await lastValueFrom(dialogRef.closed); @@ -183,32 +181,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { } }; - protected updateTaxInformation = async (): Promise => { - this.taxInfoComponent.taxFormGroup.updateValueAndValidity(); - this.taxInfoComponent.taxFormGroup.markAllAsTouched(); - - if (this.taxInfoComponent.taxFormGroup.invalid) { - return; - } - - const request = new ExpandedTaxInfoUpdateRequest(); - request.country = this.taxInfoComponent.country; - request.postalCode = this.taxInfoComponent.postalCode; - request.taxId = this.taxInfoComponent.taxId; - request.line1 = this.taxInfoComponent.line1; - request.line2 = this.taxInfoComponent.line2; - request.city = this.taxInfoComponent.city; - request.state = this.taxInfoComponent.state; - - await this.billingApiService.updateOrganizationTaxInformation(this.organizationId, request); - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("taxInfoUpdated"), - }); - }; - protected verifyBankAccount = async (request: VerifyBankAccountRequest): Promise => { await this.billingApiService.verifyOrganizationBankAccount(this.organizationId, request); this.toastService.showToast({ diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.html b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.html index e41d3d961cd..bb06f87ca03 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.html +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.html @@ -5,7 +5,12 @@ [showBankAccount]="!!organizationId" [initialPaymentMethod]="initialPaymentMethod" > - + - - diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index 298573f0852..149b4adf520 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Location } from "@angular/common"; -import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { lastValueFrom } from "rxjs"; @@ -16,7 +16,6 @@ import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/mode import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank.request"; 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 { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -29,15 +28,12 @@ import { AdjustPaymentDialogResult, openAdjustPaymentDialog, } from "./adjust-payment-dialog/adjust-payment-dialog.component"; -import { TaxInfoComponent } from "./tax-info.component"; @Component({ templateUrl: "payment-method.component.html", }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil export class PaymentMethodComponent implements OnInit, OnDestroy { - @ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent; - loading = false; firstLoaded = false; billing: BillingPaymentResponse; @@ -61,7 +57,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { ]), }); - taxForm = this.formBuilder.group({}); launchPaymentModalAutomatically = false; protected freeTrialData: FreeTrial; @@ -72,7 +67,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { protected platformUtilsService: PlatformUtilsService, private router: Router, private location: Location, - private logService: LogService, private route: ActivatedRoute, private formBuilder: FormBuilder, private dialogService: DialogService, @@ -198,15 +192,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { await this.load(); }; - submitTaxInfo = async () => { - await this.taxInfo.submitTaxInfo(); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("taxInfoUpdated"), - }); - }; - determineOrgsWithUpcomingPaymentIssues() { this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues( this.organization, @@ -231,10 +216,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { return this.organizationId != null; } - get headerClass() { - return this.forOrganization ? ["page-header"] : ["tabbed-header"]; - } - get paymentSourceClasses() { if (this.paymentSource == null) { return []; diff --git a/apps/web/src/app/billing/shared/tax-info.component.html b/apps/web/src/app/billing/shared/tax-info.component.html index 82d5104a53a..4a42c0c1109 100644 --- a/apps/web/src/app/billing/shared/tax-info.component.html +++ b/apps/web/src/app/billing/shared/tax-info.component.html @@ -13,51 +13,41 @@
    -
    +
    {{ "zipPostalCode" | i18n }}
    -
    - - - {{ "includeVAT" | i18n }} - +
    + + {{ "address1" | i18n }} + +
    -
    -
    -
    +
    + + {{ "address2" | i18n }} + + +
    +
    + + {{ "cityTown" | i18n }} + + +
    +
    + + {{ "stateProvince" | i18n }} + + +
    +
    {{ "taxIdNumber" | i18n }}
    -
    -
    - - {{ "address1" | i18n }} - - -
    -
    - - {{ "address2" | i18n }} - - -
    -
    - - {{ "cityTown" | i18n }} - - -
    -
    - - {{ "stateProvince" | i18n }} - - -
    -
    diff --git a/apps/web/src/app/billing/shared/tax-info.component.ts b/apps/web/src/app/billing/shared/tax-info.component.ts index 8ebec5e1dfe..214364e4cf2 100644 --- a/apps/web/src/app/billing/shared/tax-info.component.ts +++ b/apps/web/src/app/billing/shared/tax-info.component.ts @@ -1,31 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; +import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { CountryListItem } from "@bitwarden/common/billing/models/domain"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { TaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/tax-info-update.request"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; -import { TaxRateResponse } from "@bitwarden/common/billing/models/response/tax-rate.response"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SharedModule } from "../../shared"; -type TaxInfoView = Omit & { - includeTaxId: boolean; - [key: string]: unknown; -}; - -type CountryList = { - name: string; - value: string; - disabled: boolean; -}; - @Component({ selector: "app-tax-info", templateUrl: "tax-info.component.html", @@ -33,359 +22,68 @@ type CountryList = { imports: [SharedModule], }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class TaxInfoComponent implements OnInit { - @Input() trialFlow = false; - @Output() onCountryChanged = new EventEmitter(); +export class TaxInfoComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); + @Input() trialFlow = false; + @Output() countryChanged = new EventEmitter(); + @Output() taxInformationChanged: EventEmitter = new EventEmitter(); + taxFormGroup = new FormGroup({ - country: new FormControl(null, [Validators.required]), - postalCode: new FormControl(null), - includeTaxId: new FormControl(null), - taxId: new FormControl(null), - line1: new FormControl(null), - line2: new FormControl(null), - city: new FormControl(null), - state: new FormControl(null), + country: new FormControl(null, [Validators.required]), + postalCode: new FormControl(null, [Validators.required]), + taxId: new FormControl(null), + line1: new FormControl(null), + line2: new FormControl(null), + city: new FormControl(null), + state: new FormControl(null), }); + protected isTaxSupported: boolean; + loading = true; organizationId: string; providerId: string; - taxInfo: TaxInfoView = { - taxId: null, - line1: null, - line2: null, - city: null, - state: null, - postalCode: null, - country: "US", - includeTaxId: false, - }; - countryList: CountryList[] = [ - { name: "-- Select --", value: "", disabled: false }, - { name: "United States", value: "US", disabled: false }, - { name: "China", value: "CN", disabled: false }, - { name: "France", value: "FR", disabled: false }, - { name: "Germany", value: "DE", disabled: false }, - { name: "Canada", value: "CA", disabled: false }, - { name: "United Kingdom", value: "GB", disabled: false }, - { name: "Australia", value: "AU", disabled: false }, - { name: "India", value: "IN", disabled: false }, - { name: "", value: "-", disabled: true }, - { name: "Afghanistan", value: "AF", disabled: false }, - { name: "Åland Islands", value: "AX", disabled: false }, - { name: "Albania", value: "AL", disabled: false }, - { name: "Algeria", value: "DZ", disabled: false }, - { name: "American Samoa", value: "AS", disabled: false }, - { name: "Andorra", value: "AD", disabled: false }, - { name: "Angola", value: "AO", disabled: false }, - { name: "Anguilla", value: "AI", disabled: false }, - { name: "Antarctica", value: "AQ", disabled: false }, - { name: "Antigua and Barbuda", value: "AG", disabled: false }, - { name: "Argentina", value: "AR", disabled: false }, - { name: "Armenia", value: "AM", disabled: false }, - { name: "Aruba", value: "AW", disabled: false }, - { name: "Austria", value: "AT", disabled: false }, - { name: "Azerbaijan", value: "AZ", disabled: false }, - { name: "Bahamas", value: "BS", disabled: false }, - { name: "Bahrain", value: "BH", disabled: false }, - { name: "Bangladesh", value: "BD", disabled: false }, - { name: "Barbados", value: "BB", disabled: false }, - { name: "Belarus", value: "BY", disabled: false }, - { name: "Belgium", value: "BE", disabled: false }, - { name: "Belize", value: "BZ", disabled: false }, - { name: "Benin", value: "BJ", disabled: false }, - { name: "Bermuda", value: "BM", disabled: false }, - { name: "Bhutan", value: "BT", disabled: false }, - { name: "Bolivia, Plurinational State of", value: "BO", disabled: false }, - { name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false }, - { name: "Bosnia and Herzegovina", value: "BA", disabled: false }, - { name: "Botswana", value: "BW", disabled: false }, - { name: "Bouvet Island", value: "BV", disabled: false }, - { name: "Brazil", value: "BR", disabled: false }, - { name: "British Indian Ocean Territory", value: "IO", disabled: false }, - { name: "Brunei Darussalam", value: "BN", disabled: false }, - { name: "Bulgaria", value: "BG", disabled: false }, - { name: "Burkina Faso", value: "BF", disabled: false }, - { name: "Burundi", value: "BI", disabled: false }, - { name: "Cambodia", value: "KH", disabled: false }, - { name: "Cameroon", value: "CM", disabled: false }, - { name: "Cape Verde", value: "CV", disabled: false }, - { name: "Cayman Islands", value: "KY", disabled: false }, - { name: "Central African Republic", value: "CF", disabled: false }, - { name: "Chad", value: "TD", disabled: false }, - { name: "Chile", value: "CL", disabled: false }, - { name: "Christmas Island", value: "CX", disabled: false }, - { name: "Cocos (Keeling) Islands", value: "CC", disabled: false }, - { name: "Colombia", value: "CO", disabled: false }, - { name: "Comoros", value: "KM", disabled: false }, - { name: "Congo", value: "CG", disabled: false }, - { name: "Congo, the Democratic Republic of the", value: "CD", disabled: false }, - { name: "Cook Islands", value: "CK", disabled: false }, - { name: "Costa Rica", value: "CR", disabled: false }, - { name: "Côte d'Ivoire", value: "CI", disabled: false }, - { name: "Croatia", value: "HR", disabled: false }, - { name: "Cuba", value: "CU", disabled: false }, - { name: "Curaçao", value: "CW", disabled: false }, - { name: "Cyprus", value: "CY", disabled: false }, - { name: "Czech Republic", value: "CZ", disabled: false }, - { name: "Denmark", value: "DK", disabled: false }, - { name: "Djibouti", value: "DJ", disabled: false }, - { name: "Dominica", value: "DM", disabled: false }, - { name: "Dominican Republic", value: "DO", disabled: false }, - { name: "Ecuador", value: "EC", disabled: false }, - { name: "Egypt", value: "EG", disabled: false }, - { name: "El Salvador", value: "SV", disabled: false }, - { name: "Equatorial Guinea", value: "GQ", disabled: false }, - { name: "Eritrea", value: "ER", disabled: false }, - { name: "Estonia", value: "EE", disabled: false }, - { name: "Ethiopia", value: "ET", disabled: false }, - { name: "Falkland Islands (Malvinas)", value: "FK", disabled: false }, - { name: "Faroe Islands", value: "FO", disabled: false }, - { name: "Fiji", value: "FJ", disabled: false }, - { name: "Finland", value: "FI", disabled: false }, - { name: "French Guiana", value: "GF", disabled: false }, - { name: "French Polynesia", value: "PF", disabled: false }, - { name: "French Southern Territories", value: "TF", disabled: false }, - { name: "Gabon", value: "GA", disabled: false }, - { name: "Gambia", value: "GM", disabled: false }, - { name: "Georgia", value: "GE", disabled: false }, - { name: "Ghana", value: "GH", disabled: false }, - { name: "Gibraltar", value: "GI", disabled: false }, - { name: "Greece", value: "GR", disabled: false }, - { name: "Greenland", value: "GL", disabled: false }, - { name: "Grenada", value: "GD", disabled: false }, - { name: "Guadeloupe", value: "GP", disabled: false }, - { name: "Guam", value: "GU", disabled: false }, - { name: "Guatemala", value: "GT", disabled: false }, - { name: "Guernsey", value: "GG", disabled: false }, - { name: "Guinea", value: "GN", disabled: false }, - { name: "Guinea-Bissau", value: "GW", disabled: false }, - { name: "Guyana", value: "GY", disabled: false }, - { name: "Haiti", value: "HT", disabled: false }, - { name: "Heard Island and McDonald Islands", value: "HM", disabled: false }, - { name: "Holy See (Vatican City State)", value: "VA", disabled: false }, - { name: "Honduras", value: "HN", disabled: false }, - { name: "Hong Kong", value: "HK", disabled: false }, - { name: "Hungary", value: "HU", disabled: false }, - { name: "Iceland", value: "IS", disabled: false }, - { name: "Indonesia", value: "ID", disabled: false }, - { name: "Iran, Islamic Republic of", value: "IR", disabled: false }, - { name: "Iraq", value: "IQ", disabled: false }, - { name: "Ireland", value: "IE", disabled: false }, - { name: "Isle of Man", value: "IM", disabled: false }, - { name: "Israel", value: "IL", disabled: false }, - { name: "Italy", value: "IT", disabled: false }, - { name: "Jamaica", value: "JM", disabled: false }, - { name: "Japan", value: "JP", disabled: false }, - { name: "Jersey", value: "JE", disabled: false }, - { name: "Jordan", value: "JO", disabled: false }, - { name: "Kazakhstan", value: "KZ", disabled: false }, - { name: "Kenya", value: "KE", disabled: false }, - { name: "Kiribati", value: "KI", disabled: false }, - { name: "Korea, Democratic People's Republic of", value: "KP", disabled: false }, - { name: "Korea, Republic of", value: "KR", disabled: false }, - { name: "Kuwait", value: "KW", disabled: false }, - { name: "Kyrgyzstan", value: "KG", disabled: false }, - { name: "Lao People's Democratic Republic", value: "LA", disabled: false }, - { name: "Latvia", value: "LV", disabled: false }, - { name: "Lebanon", value: "LB", disabled: false }, - { name: "Lesotho", value: "LS", disabled: false }, - { name: "Liberia", value: "LR", disabled: false }, - { name: "Libya", value: "LY", disabled: false }, - { name: "Liechtenstein", value: "LI", disabled: false }, - { name: "Lithuania", value: "LT", disabled: false }, - { name: "Luxembourg", value: "LU", disabled: false }, - { name: "Macao", value: "MO", disabled: false }, - { name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false }, - { name: "Madagascar", value: "MG", disabled: false }, - { name: "Malawi", value: "MW", disabled: false }, - { name: "Malaysia", value: "MY", disabled: false }, - { name: "Maldives", value: "MV", disabled: false }, - { name: "Mali", value: "ML", disabled: false }, - { name: "Malta", value: "MT", disabled: false }, - { name: "Marshall Islands", value: "MH", disabled: false }, - { name: "Martinique", value: "MQ", disabled: false }, - { name: "Mauritania", value: "MR", disabled: false }, - { name: "Mauritius", value: "MU", disabled: false }, - { name: "Mayotte", value: "YT", disabled: false }, - { name: "Mexico", value: "MX", disabled: false }, - { name: "Micronesia, Federated States of", value: "FM", disabled: false }, - { name: "Moldova, Republic of", value: "MD", disabled: false }, - { name: "Monaco", value: "MC", disabled: false }, - { name: "Mongolia", value: "MN", disabled: false }, - { name: "Montenegro", value: "ME", disabled: false }, - { name: "Montserrat", value: "MS", disabled: false }, - { name: "Morocco", value: "MA", disabled: false }, - { name: "Mozambique", value: "MZ", disabled: false }, - { name: "Myanmar", value: "MM", disabled: false }, - { name: "Namibia", value: "NA", disabled: false }, - { name: "Nauru", value: "NR", disabled: false }, - { name: "Nepal", value: "NP", disabled: false }, - { name: "Netherlands", value: "NL", disabled: false }, - { name: "New Caledonia", value: "NC", disabled: false }, - { name: "New Zealand", value: "NZ", disabled: false }, - { name: "Nicaragua", value: "NI", disabled: false }, - { name: "Niger", value: "NE", disabled: false }, - { name: "Nigeria", value: "NG", disabled: false }, - { name: "Niue", value: "NU", disabled: false }, - { name: "Norfolk Island", value: "NF", disabled: false }, - { name: "Northern Mariana Islands", value: "MP", disabled: false }, - { name: "Norway", value: "NO", disabled: false }, - { name: "Oman", value: "OM", disabled: false }, - { name: "Pakistan", value: "PK", disabled: false }, - { name: "Palau", value: "PW", disabled: false }, - { name: "Palestinian Territory, Occupied", value: "PS", disabled: false }, - { name: "Panama", value: "PA", disabled: false }, - { name: "Papua New Guinea", value: "PG", disabled: false }, - { name: "Paraguay", value: "PY", disabled: false }, - { name: "Peru", value: "PE", disabled: false }, - { name: "Philippines", value: "PH", disabled: false }, - { name: "Pitcairn", value: "PN", disabled: false }, - { name: "Poland", value: "PL", disabled: false }, - { name: "Portugal", value: "PT", disabled: false }, - { name: "Puerto Rico", value: "PR", disabled: false }, - { name: "Qatar", value: "QA", disabled: false }, - { name: "Réunion", value: "RE", disabled: false }, - { name: "Romania", value: "RO", disabled: false }, - { name: "Russian Federation", value: "RU", disabled: false }, - { name: "Rwanda", value: "RW", disabled: false }, - { name: "Saint Barthélemy", value: "BL", disabled: false }, - { name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false }, - { name: "Saint Kitts and Nevis", value: "KN", disabled: false }, - { name: "Saint Lucia", value: "LC", disabled: false }, - { name: "Saint Martin (French part)", value: "MF", disabled: false }, - { name: "Saint Pierre and Miquelon", value: "PM", disabled: false }, - { name: "Saint Vincent and the Grenadines", value: "VC", disabled: false }, - { name: "Samoa", value: "WS", disabled: false }, - { name: "San Marino", value: "SM", disabled: false }, - { name: "Sao Tome and Principe", value: "ST", disabled: false }, - { name: "Saudi Arabia", value: "SA", disabled: false }, - { name: "Senegal", value: "SN", disabled: false }, - { name: "Serbia", value: "RS", disabled: false }, - { name: "Seychelles", value: "SC", disabled: false }, - { name: "Sierra Leone", value: "SL", disabled: false }, - { name: "Singapore", value: "SG", disabled: false }, - { name: "Sint Maarten (Dutch part)", value: "SX", disabled: false }, - { name: "Slovakia", value: "SK", disabled: false }, - { name: "Slovenia", value: "SI", disabled: false }, - { name: "Solomon Islands", value: "SB", disabled: false }, - { name: "Somalia", value: "SO", disabled: false }, - { name: "South Africa", value: "ZA", disabled: false }, - { name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false }, - { name: "South Sudan", value: "SS", disabled: false }, - { name: "Spain", value: "ES", disabled: false }, - { name: "Sri Lanka", value: "LK", disabled: false }, - { name: "Sudan", value: "SD", disabled: false }, - { name: "Suriname", value: "SR", disabled: false }, - { name: "Svalbard and Jan Mayen", value: "SJ", disabled: false }, - { name: "Swaziland", value: "SZ", disabled: false }, - { name: "Sweden", value: "SE", disabled: false }, - { name: "Switzerland", value: "CH", disabled: false }, - { name: "Syrian Arab Republic", value: "SY", disabled: false }, - { name: "Taiwan", value: "TW", disabled: false }, - { name: "Tajikistan", value: "TJ", disabled: false }, - { name: "Tanzania, United Republic of", value: "TZ", disabled: false }, - { name: "Thailand", value: "TH", disabled: false }, - { name: "Timor-Leste", value: "TL", disabled: false }, - { name: "Togo", value: "TG", disabled: false }, - { name: "Tokelau", value: "TK", disabled: false }, - { name: "Tonga", value: "TO", disabled: false }, - { name: "Trinidad and Tobago", value: "TT", disabled: false }, - { name: "Tunisia", value: "TN", disabled: false }, - { name: "Turkey", value: "TR", disabled: false }, - { name: "Turkmenistan", value: "TM", disabled: false }, - { name: "Turks and Caicos Islands", value: "TC", disabled: false }, - { name: "Tuvalu", value: "TV", disabled: false }, - { name: "Uganda", value: "UG", disabled: false }, - { name: "Ukraine", value: "UA", disabled: false }, - { name: "United Arab Emirates", value: "AE", disabled: false }, - { name: "United States Minor Outlying Islands", value: "UM", disabled: false }, - { name: "Uruguay", value: "UY", disabled: false }, - { name: "Uzbekistan", value: "UZ", disabled: false }, - { name: "Vanuatu", value: "VU", disabled: false }, - { name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false }, - { name: "Viet Nam", value: "VN", disabled: false }, - { name: "Virgin Islands, British", value: "VG", disabled: false }, - { name: "Virgin Islands, U.S.", value: "VI", disabled: false }, - { name: "Wallis and Futuna", value: "WF", disabled: false }, - { name: "Western Sahara", value: "EH", disabled: false }, - { name: "Yemen", value: "YE", disabled: false }, - { name: "Zambia", value: "ZM", disabled: false }, - { name: "Zimbabwe", value: "ZW", disabled: false }, - ]; - taxRates: TaxRateResponse[]; + countryList: CountryListItem[] = this.taxService.getCountries(); constructor( private apiService: ApiService, private route: ActivatedRoute, private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, + private taxService: TaxServiceAbstraction, ) {} get country(): string { - return this.taxFormGroup.get("country").value; - } - - set country(country: string) { - this.taxFormGroup.get("country").setValue(country); + return this.taxFormGroup.controls.country.value; } get postalCode(): string { - return this.taxFormGroup.get("postalCode").value; - } - - set postalCode(postalCode: string) { - this.taxFormGroup.get("postalCode").setValue(postalCode); - } - - get includeTaxId(): boolean { - return this.taxFormGroup.get("includeTaxId").value; - } - - set includeTaxId(includeTaxId: boolean) { - this.taxFormGroup.get("includeTaxId").setValue(includeTaxId); + return this.taxFormGroup.controls.postalCode.value; } get taxId(): string { - return this.taxFormGroup.get("taxId").value; - } - - set taxId(taxId: string) { - this.taxFormGroup.get("taxId").setValue(taxId); + return this.taxFormGroup.controls.taxId.value; } get line1(): string { - return this.taxFormGroup.get("line1").value; - } - - set line1(line1: string) { - this.taxFormGroup.get("line1").setValue(line1); + return this.taxFormGroup.controls.line1.value; } get line2(): string { - return this.taxFormGroup.get("line2").value; - } - - set line2(line2: string) { - this.taxFormGroup.get("line2").setValue(line2); + return this.taxFormGroup.controls.line2.value; } get city(): string { - return this.taxFormGroup.get("city").value; - } - - set city(city: string) { - this.taxFormGroup.get("city").setValue(city); + return this.taxFormGroup.controls.city.value; } get state(): string { - return this.taxFormGroup.get("state").value; + return this.taxFormGroup.controls.state.value; } - set state(state: string) { - this.taxFormGroup.get("state").setValue(state); + get showTaxIdField(): boolean { + return !!this.organizationId; } async ngOnInit() { @@ -402,22 +100,13 @@ export class TaxInfoComponent implements OnInit { try { const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); if (taxInfo) { - this.taxId = taxInfo.taxId; - this.state = taxInfo.state; - this.line1 = taxInfo.line1; - this.line2 = taxInfo.line2; - this.city = taxInfo.city; - this.state = taxInfo.state; - this.postalCode = taxInfo.postalCode; - this.country = taxInfo.country || "US"; - this.includeTaxId = - this.countrySupportsTax(this.country) && - (!!taxInfo.taxId || - !!taxInfo.line1 || - !!taxInfo.line2 || - !!taxInfo.city || - !!taxInfo.state); - this.setTaxInfoObject(); + this.taxFormGroup.controls.taxId.setValue(taxInfo.taxId); + this.taxFormGroup.controls.state.setValue(taxInfo.state); + this.taxFormGroup.controls.line1.setValue(taxInfo.line1); + this.taxFormGroup.controls.line2.setValue(taxInfo.line2); + this.taxFormGroup.controls.city.setValue(taxInfo.city); + this.taxFormGroup.controls.postalCode.setValue(taxInfo.postalCode); + this.taxFormGroup.controls.country.setValue(taxInfo.country); } } catch (e) { this.logService.error(e); @@ -426,119 +115,79 @@ export class TaxInfoComponent implements OnInit { try { const taxInfo = await this.apiService.getTaxInfo(); if (taxInfo) { - this.postalCode = taxInfo.postalCode; - this.country = taxInfo.country || "US"; + this.taxFormGroup.controls.postalCode.setValue(taxInfo.postalCode); + this.taxFormGroup.controls.country.setValue(taxInfo.country); } - this.setTaxInfoObject(); } catch (e) { this.logService.error(e); } } - if (this.country === "US") { - this.taxFormGroup.get("postalCode").setValidators([Validators.required]); - this.taxFormGroup.get("postalCode").updateValueAndValidity(); - } + this.isTaxSupported = await this.taxService.isCountrySupported( + this.taxFormGroup.controls.country.value, + ); - if (this.country !== "US") { - this.onCountryChanged.emit(); - } + this.countryChanged.emit(); }); - this.taxFormGroup - .get("country") - .valueChanges.pipe(takeUntil(this.destroy$)) + this.taxFormGroup.controls.country.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) .subscribe((value) => { - if (value === "US") { - this.taxFormGroup.get("postalCode").setValidators([Validators.required]); - } else { - this.taxFormGroup.get("postalCode").clearValidators(); - } - this.taxFormGroup.get("postalCode").updateValueAndValidity(); - this.setTaxInfoObject(); - this.changeCountry(); + this.taxService + .isCountrySupported(this.taxFormGroup.controls.country.value) + .then((isSupported) => { + this.isTaxSupported = isSupported; + }) + .catch(() => { + this.isTaxSupported = false; + }) + .finally(() => { + if (!this.isTaxSupported) { + this.taxFormGroup.controls.taxId.setValue(null); + this.taxFormGroup.controls.line1.setValue(null); + this.taxFormGroup.controls.line2.setValue(null); + this.taxFormGroup.controls.city.setValue(null); + this.taxFormGroup.controls.state.setValue(null); + } + + this.countryChanged.emit(); + }); + this.taxInformationChanged.emit(); }); - try { - const taxRates = await this.apiService.getTaxRates(); - if (taxRates) { - this.taxRates = taxRates.data; - } - } catch (e) { - this.logService.error(e); - } finally { - this.loading = false; - } + this.taxFormGroup.controls.postalCode.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + this.taxInformationChanged.emit(); + }); + + this.taxFormGroup.controls.taxId.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + this.taxInformationChanged.emit(); + }); + + this.loading = false; } - get taxRate() { - if (this.taxRates != null) { - const localTaxRate = this.taxRates.find( - (x) => x.country === this.country && x.postalCode === this.postalCode, - ); - return localTaxRate?.rate ?? null; - } - } - - setTaxInfoObject() { - this.taxInfo.country = this.country; - this.taxInfo.postalCode = this.postalCode; - this.taxInfo.includeTaxId = this.includeTaxId; - this.taxInfo.taxId = this.taxId; - this.taxInfo.line1 = this.line1; - this.taxInfo.line2 = this.line2; - this.taxInfo.city = this.city; - this.taxInfo.state = this.state; - } - - get showTaxIdCheckbox() { - return ( - (this.organizationId || this.providerId) && - this.country !== "US" && - this.countrySupportsTax(this.taxInfo.country) - ); - } - - get showTaxIdFields() { - return ( - (this.organizationId || this.providerId) && - this.includeTaxId && - this.countrySupportsTax(this.country) - ); - } - - getTaxInfoRequest(): TaxInfoUpdateRequest { - if (this.organizationId || this.providerId) { - const request = new ExpandedTaxInfoUpdateRequest(); - request.country = this.country; - request.postalCode = this.postalCode; - - if (this.includeTaxId) { - request.taxId = this.taxId; - request.line1 = this.line1; - request.line2 = this.line2; - request.city = this.city; - request.state = this.state; - } else { - request.taxId = null; - request.line1 = null; - request.line2 = null; - request.city = null; - request.state = null; - } - return request; - } else { - const request = new TaxInfoUpdateRequest(); - request.postalCode = this.postalCode; - request.country = this.country; - return request; - } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } submitTaxInfo(): Promise { this.taxFormGroup.updateValueAndValidity(); this.taxFormGroup.markAllAsTouched(); - const request = this.getTaxInfoRequest(); + + const request = new ExpandedTaxInfoUpdateRequest(); + request.country = this.country; + request.postalCode = this.postalCode; + request.taxId = this.taxId; + request.line1 = this.line1; + request.line2 = this.line2; + request.city = this.city; + request.state = this.state; + return this.organizationId ? this.organizationApiService.updateTaxInfo( this.organizationId, @@ -546,97 +195,4 @@ export class TaxInfoComponent implements OnInit { ) : this.apiService.putTaxInfo(request); } - - changeCountry() { - if (!this.countrySupportsTax(this.country)) { - this.includeTaxId = false; - this.taxId = null; - this.line1 = null; - this.line2 = null; - this.city = null; - this.state = null; - this.setTaxInfoObject(); - } - this.onCountryChanged.emit(); - } - - countrySupportsTax(countryCode: string) { - return this.taxSupportedCountryCodes.includes(countryCode); - } - - private taxSupportedCountryCodes: string[] = [ - "CN", - "FR", - "DE", - "CA", - "GB", - "AU", - "IN", - "AD", - "AR", - "AT", - "BE", - "BO", - "BR", - "BG", - "CL", - "CO", - "CR", - "HR", - "CY", - "CZ", - "DK", - "DO", - "EC", - "EG", - "SV", - "EE", - "FI", - "GE", - "GR", - "HK", - "HU", - "IS", - "ID", - "IQ", - "IE", - "IL", - "IT", - "JP", - "KE", - "KR", - "LV", - "LI", - "LT", - "LU", - "MY", - "MT", - "MX", - "NL", - "NZ", - "NO", - "PE", - "PH", - "PL", - "PT", - "RO", - "RU", - "SA", - "RS", - "SG", - "SK", - "SI", - "ZA", - "ES", - "SE", - "CH", - "TW", - "TH", - "TR", - "UA", - "AE", - "UY", - "VE", - "VN", - ]; } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 7c338fc6e97..08e08ccad15 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9275,6 +9275,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index 33a20444c2b..74aa468c42e 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -29,7 +29,7 @@
    - diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index 46fd6989681..f773db6c11c 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -111,9 +111,7 @@ export class SetupComponent implements OnInit, OnDestroy { try { this.formGroup.markAllAsTouched(); - const formIsValid = this.formGroup.valid && this.manageTaxInformationComponent.touch(); - - if (!formIsValid) { + if (!this.manageTaxInformationComponent.validate() || !this.formGroup.valid) { return; } @@ -131,14 +129,11 @@ export class SetupComponent implements OnInit, OnDestroy { request.taxInfo.country = taxInformation.country; request.taxInfo.postalCode = taxInformation.postalCode; - - if (taxInformation.includeTaxId) { - request.taxInfo.taxId = taxInformation.taxId; - request.taxInfo.line1 = taxInformation.line1; - request.taxInfo.line2 = taxInformation.line2; - request.taxInfo.city = taxInformation.city; - request.taxInfo.state = taxInformation.state; - } + request.taxInfo.taxId = taxInformation.taxId; + request.taxInfo.line1 = taxInformation.line1; + request.taxInfo.line2 = taxInformation.line2; + request.taxInfo.city = taxInformation.city; + request.taxInfo.state = taxInformation.state; const provider = await this.providerApiService.postProviderSetup(this.providerId, request); diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html index 0b041bd4c06..3f635656fb7 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html @@ -1,7 +1,7 @@
    - + {{ "country" | i18n }}
    - + {{ "zipPostalCode" | i18n }}
    -
    - - - {{ "includeVAT" | i18n }} - + +
    + + {{ "address1" | i18n }} + + +
    +
    + + {{ "address2" | i18n }} + + +
    +
    + + {{ "cityTown" | i18n }} + + +
    +
    + + {{ "stateProvince" | i18n }} + + +
    +
    + + {{ "taxIdNumber" | i18n }} + + +
    +
    +
    +
    -
    -
    - - {{ "taxIdNumber" | i18n }} - - -
    -
    -
    -
    - - {{ "address1" | i18n }} - - -
    -
    - - {{ "address2" | i18n }} - - -
    -
    - - {{ "cityTown" | i18n }} - - -
    -
    - - {{ "stateProvince" | i18n }} - - -
    -
    - diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index a3564b1ebc9..13a6d2d0cc3 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -3,14 +3,10 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; +import { debounceTime } from "rxjs/operators"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; - -type Country = { - name: string; - value: string; - disabled: boolean; -}; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/models/domain"; @Component({ selector: "app-manage-tax-information", @@ -19,12 +15,23 @@ type Country = { export class ManageTaxInformationComponent implements OnInit, OnDestroy { @Input() startWith: TaxInformation; @Input() onSubmit?: (taxInformation: TaxInformation) => Promise; + @Input() showTaxIdField: boolean = true; + + /** + * Emits when the tax information has changed. + */ + @Output() taxInformationChanged = new EventEmitter(); + + /** + * Emits when the tax information has been updated. + */ @Output() taxInformationUpdated = new EventEmitter(); + private taxInformation: TaxInformation; + protected formGroup = this.formBuilder.group({ country: ["", Validators.required], postalCode: ["", Validators.required], - includeTaxId: false, taxId: "", line1: "", line2: "", @@ -32,16 +39,20 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { state: "", }); + protected isTaxSupported: boolean; + private destroy$ = new Subject(); - private taxInformation: TaxInformation; + protected readonly countries: CountryListItem[] = this.taxService.getCountries(); - constructor(private formBuilder: FormBuilder) {} + constructor( + private formBuilder: FormBuilder, + private taxService: TaxServiceAbstraction, + ) {} - getTaxInformation = (): TaxInformation & { includeTaxId: boolean } => ({ - ...this.taxInformation, - includeTaxId: this.formGroup.value.includeTaxId, - }); + getTaxInformation(): TaxInformation { + return this.taxInformation; + } submit = async () => { this.formGroup.markAllAsTouched(); @@ -52,23 +63,32 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { this.taxInformationUpdated.emit(); }; - touch = (): boolean => { - this.formGroup.markAllAsTouched(); - return this.formGroup.valid; - }; + validate(): boolean { + if (this.formGroup.dirty) { + this.formGroup.markAllAsTouched(); + return this.formGroup.valid; + } else { + return this.formGroup.valid; + } + } async ngOnInit() { if (this.startWith) { - this.formGroup.patchValue({ - ...this.startWith, - includeTaxId: - this.countrySupportsTax(this.startWith.country) && - (!!this.startWith.taxId || - !!this.startWith.line1 || - !!this.startWith.line2 || - !!this.startWith.city || - !!this.startWith.state), - }); + this.formGroup.controls.country.setValue(this.startWith.country); + this.formGroup.controls.postalCode.setValue(this.startWith.postalCode); + + this.isTaxSupported = + this.startWith && this.startWith.country + ? await this.taxService.isCountrySupported(this.startWith.country) + : false; + + if (this.isTaxSupported) { + this.formGroup.controls.taxId.setValue(this.startWith.taxId); + this.formGroup.controls.line1.setValue(this.startWith.line1); + this.formGroup.controls.line2.setValue(this.startWith.line2); + this.formGroup.controls.city.setValue(this.startWith.city); + this.formGroup.controls.state.setValue(this.startWith.state); + } } this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((values) => { @@ -82,354 +102,47 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { state: values.state, }; }); + + this.formGroup.controls.country.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe((country: string) => { + this.taxService + .isCountrySupported(country) + .then((isSupported) => (this.isTaxSupported = isSupported)) + .catch(() => (this.isTaxSupported = false)) + .finally(() => { + if (!this.isTaxSupported) { + this.formGroup.controls.taxId.setValue(null); + this.formGroup.controls.line1.setValue(null); + this.formGroup.controls.line2.setValue(null); + this.formGroup.controls.city.setValue(null); + this.formGroup.controls.state.setValue(null); + } + if (this.taxInformationChanged) { + this.taxInformationChanged.emit(this.taxInformation); + } + }); + }); + + this.formGroup.controls.postalCode.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + if (this.taxInformationChanged) { + this.taxInformationChanged.emit(this.taxInformation); + } + }); + + this.formGroup.controls.taxId.valueChanges + .pipe(debounceTime(1000), takeUntil(this.destroy$)) + .subscribe(() => { + if (this.taxInformationChanged) { + this.taxInformationChanged.emit(this.taxInformation); + } + }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } - - protected countrySupportsTax(countryCode: string) { - return this.taxSupportedCountryCodes.includes(countryCode); - } - - protected get includeTaxIdIsSelected() { - return this.formGroup.value.includeTaxId; - } - - protected get selectionSupportsAdditionalOptions() { - return ( - this.formGroup.value.country !== "US" && this.countrySupportsTax(this.formGroup.value.country) - ); - } - - protected countries: Country[] = [ - { name: "-- Select --", value: "", disabled: false }, - { name: "United States", value: "US", disabled: false }, - { name: "China", value: "CN", disabled: false }, - { name: "France", value: "FR", disabled: false }, - { name: "Germany", value: "DE", disabled: false }, - { name: "Canada", value: "CA", disabled: false }, - { name: "United Kingdom", value: "GB", disabled: false }, - { name: "Australia", value: "AU", disabled: false }, - { name: "India", value: "IN", disabled: false }, - { name: "", value: "-", disabled: true }, - { name: "Afghanistan", value: "AF", disabled: false }, - { name: "Åland Islands", value: "AX", disabled: false }, - { name: "Albania", value: "AL", disabled: false }, - { name: "Algeria", value: "DZ", disabled: false }, - { name: "American Samoa", value: "AS", disabled: false }, - { name: "Andorra", value: "AD", disabled: false }, - { name: "Angola", value: "AO", disabled: false }, - { name: "Anguilla", value: "AI", disabled: false }, - { name: "Antarctica", value: "AQ", disabled: false }, - { name: "Antigua and Barbuda", value: "AG", disabled: false }, - { name: "Argentina", value: "AR", disabled: false }, - { name: "Armenia", value: "AM", disabled: false }, - { name: "Aruba", value: "AW", disabled: false }, - { name: "Austria", value: "AT", disabled: false }, - { name: "Azerbaijan", value: "AZ", disabled: false }, - { name: "Bahamas", value: "BS", disabled: false }, - { name: "Bahrain", value: "BH", disabled: false }, - { name: "Bangladesh", value: "BD", disabled: false }, - { name: "Barbados", value: "BB", disabled: false }, - { name: "Belarus", value: "BY", disabled: false }, - { name: "Belgium", value: "BE", disabled: false }, - { name: "Belize", value: "BZ", disabled: false }, - { name: "Benin", value: "BJ", disabled: false }, - { name: "Bermuda", value: "BM", disabled: false }, - { name: "Bhutan", value: "BT", disabled: false }, - { name: "Bolivia, Plurinational State of", value: "BO", disabled: false }, - { name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false }, - { name: "Bosnia and Herzegovina", value: "BA", disabled: false }, - { name: "Botswana", value: "BW", disabled: false }, - { name: "Bouvet Island", value: "BV", disabled: false }, - { name: "Brazil", value: "BR", disabled: false }, - { name: "British Indian Ocean Territory", value: "IO", disabled: false }, - { name: "Brunei Darussalam", value: "BN", disabled: false }, - { name: "Bulgaria", value: "BG", disabled: false }, - { name: "Burkina Faso", value: "BF", disabled: false }, - { name: "Burundi", value: "BI", disabled: false }, - { name: "Cambodia", value: "KH", disabled: false }, - { name: "Cameroon", value: "CM", disabled: false }, - { name: "Cape Verde", value: "CV", disabled: false }, - { name: "Cayman Islands", value: "KY", disabled: false }, - { name: "Central African Republic", value: "CF", disabled: false }, - { name: "Chad", value: "TD", disabled: false }, - { name: "Chile", value: "CL", disabled: false }, - { name: "Christmas Island", value: "CX", disabled: false }, - { name: "Cocos (Keeling) Islands", value: "CC", disabled: false }, - { name: "Colombia", value: "CO", disabled: false }, - { name: "Comoros", value: "KM", disabled: false }, - { name: "Congo", value: "CG", disabled: false }, - { name: "Congo, the Democratic Republic of the", value: "CD", disabled: false }, - { name: "Cook Islands", value: "CK", disabled: false }, - { name: "Costa Rica", value: "CR", disabled: false }, - { name: "Côte d'Ivoire", value: "CI", disabled: false }, - { name: "Croatia", value: "HR", disabled: false }, - { name: "Cuba", value: "CU", disabled: false }, - { name: "Curaçao", value: "CW", disabled: false }, - { name: "Cyprus", value: "CY", disabled: false }, - { name: "Czech Republic", value: "CZ", disabled: false }, - { name: "Denmark", value: "DK", disabled: false }, - { name: "Djibouti", value: "DJ", disabled: false }, - { name: "Dominica", value: "DM", disabled: false }, - { name: "Dominican Republic", value: "DO", disabled: false }, - { name: "Ecuador", value: "EC", disabled: false }, - { name: "Egypt", value: "EG", disabled: false }, - { name: "El Salvador", value: "SV", disabled: false }, - { name: "Equatorial Guinea", value: "GQ", disabled: false }, - { name: "Eritrea", value: "ER", disabled: false }, - { name: "Estonia", value: "EE", disabled: false }, - { name: "Ethiopia", value: "ET", disabled: false }, - { name: "Falkland Islands (Malvinas)", value: "FK", disabled: false }, - { name: "Faroe Islands", value: "FO", disabled: false }, - { name: "Fiji", value: "FJ", disabled: false }, - { name: "Finland", value: "FI", disabled: false }, - { name: "French Guiana", value: "GF", disabled: false }, - { name: "French Polynesia", value: "PF", disabled: false }, - { name: "French Southern Territories", value: "TF", disabled: false }, - { name: "Gabon", value: "GA", disabled: false }, - { name: "Gambia", value: "GM", disabled: false }, - { name: "Georgia", value: "GE", disabled: false }, - { name: "Ghana", value: "GH", disabled: false }, - { name: "Gibraltar", value: "GI", disabled: false }, - { name: "Greece", value: "GR", disabled: false }, - { name: "Greenland", value: "GL", disabled: false }, - { name: "Grenada", value: "GD", disabled: false }, - { name: "Guadeloupe", value: "GP", disabled: false }, - { name: "Guam", value: "GU", disabled: false }, - { name: "Guatemala", value: "GT", disabled: false }, - { name: "Guernsey", value: "GG", disabled: false }, - { name: "Guinea", value: "GN", disabled: false }, - { name: "Guinea-Bissau", value: "GW", disabled: false }, - { name: "Guyana", value: "GY", disabled: false }, - { name: "Haiti", value: "HT", disabled: false }, - { name: "Heard Island and McDonald Islands", value: "HM", disabled: false }, - { name: "Holy See (Vatican City State)", value: "VA", disabled: false }, - { name: "Honduras", value: "HN", disabled: false }, - { name: "Hong Kong", value: "HK", disabled: false }, - { name: "Hungary", value: "HU", disabled: false }, - { name: "Iceland", value: "IS", disabled: false }, - { name: "Indonesia", value: "ID", disabled: false }, - { name: "Iran, Islamic Republic of", value: "IR", disabled: false }, - { name: "Iraq", value: "IQ", disabled: false }, - { name: "Ireland", value: "IE", disabled: false }, - { name: "Isle of Man", value: "IM", disabled: false }, - { name: "Israel", value: "IL", disabled: false }, - { name: "Italy", value: "IT", disabled: false }, - { name: "Jamaica", value: "JM", disabled: false }, - { name: "Japan", value: "JP", disabled: false }, - { name: "Jersey", value: "JE", disabled: false }, - { name: "Jordan", value: "JO", disabled: false }, - { name: "Kazakhstan", value: "KZ", disabled: false }, - { name: "Kenya", value: "KE", disabled: false }, - { name: "Kiribati", value: "KI", disabled: false }, - { name: "Korea, Democratic People's Republic of", value: "KP", disabled: false }, - { name: "Korea, Republic of", value: "KR", disabled: false }, - { name: "Kuwait", value: "KW", disabled: false }, - { name: "Kyrgyzstan", value: "KG", disabled: false }, - { name: "Lao People's Democratic Republic", value: "LA", disabled: false }, - { name: "Latvia", value: "LV", disabled: false }, - { name: "Lebanon", value: "LB", disabled: false }, - { name: "Lesotho", value: "LS", disabled: false }, - { name: "Liberia", value: "LR", disabled: false }, - { name: "Libya", value: "LY", disabled: false }, - { name: "Liechtenstein", value: "LI", disabled: false }, - { name: "Lithuania", value: "LT", disabled: false }, - { name: "Luxembourg", value: "LU", disabled: false }, - { name: "Macao", value: "MO", disabled: false }, - { name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false }, - { name: "Madagascar", value: "MG", disabled: false }, - { name: "Malawi", value: "MW", disabled: false }, - { name: "Malaysia", value: "MY", disabled: false }, - { name: "Maldives", value: "MV", disabled: false }, - { name: "Mali", value: "ML", disabled: false }, - { name: "Malta", value: "MT", disabled: false }, - { name: "Marshall Islands", value: "MH", disabled: false }, - { name: "Martinique", value: "MQ", disabled: false }, - { name: "Mauritania", value: "MR", disabled: false }, - { name: "Mauritius", value: "MU", disabled: false }, - { name: "Mayotte", value: "YT", disabled: false }, - { name: "Mexico", value: "MX", disabled: false }, - { name: "Micronesia, Federated States of", value: "FM", disabled: false }, - { name: "Moldova, Republic of", value: "MD", disabled: false }, - { name: "Monaco", value: "MC", disabled: false }, - { name: "Mongolia", value: "MN", disabled: false }, - { name: "Montenegro", value: "ME", disabled: false }, - { name: "Montserrat", value: "MS", disabled: false }, - { name: "Morocco", value: "MA", disabled: false }, - { name: "Mozambique", value: "MZ", disabled: false }, - { name: "Myanmar", value: "MM", disabled: false }, - { name: "Namibia", value: "NA", disabled: false }, - { name: "Nauru", value: "NR", disabled: false }, - { name: "Nepal", value: "NP", disabled: false }, - { name: "Netherlands", value: "NL", disabled: false }, - { name: "New Caledonia", value: "NC", disabled: false }, - { name: "New Zealand", value: "NZ", disabled: false }, - { name: "Nicaragua", value: "NI", disabled: false }, - { name: "Niger", value: "NE", disabled: false }, - { name: "Nigeria", value: "NG", disabled: false }, - { name: "Niue", value: "NU", disabled: false }, - { name: "Norfolk Island", value: "NF", disabled: false }, - { name: "Northern Mariana Islands", value: "MP", disabled: false }, - { name: "Norway", value: "NO", disabled: false }, - { name: "Oman", value: "OM", disabled: false }, - { name: "Pakistan", value: "PK", disabled: false }, - { name: "Palau", value: "PW", disabled: false }, - { name: "Palestinian Territory, Occupied", value: "PS", disabled: false }, - { name: "Panama", value: "PA", disabled: false }, - { name: "Papua New Guinea", value: "PG", disabled: false }, - { name: "Paraguay", value: "PY", disabled: false }, - { name: "Peru", value: "PE", disabled: false }, - { name: "Philippines", value: "PH", disabled: false }, - { name: "Pitcairn", value: "PN", disabled: false }, - { name: "Poland", value: "PL", disabled: false }, - { name: "Portugal", value: "PT", disabled: false }, - { name: "Puerto Rico", value: "PR", disabled: false }, - { name: "Qatar", value: "QA", disabled: false }, - { name: "Réunion", value: "RE", disabled: false }, - { name: "Romania", value: "RO", disabled: false }, - { name: "Russian Federation", value: "RU", disabled: false }, - { name: "Rwanda", value: "RW", disabled: false }, - { name: "Saint Barthélemy", value: "BL", disabled: false }, - { name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false }, - { name: "Saint Kitts and Nevis", value: "KN", disabled: false }, - { name: "Saint Lucia", value: "LC", disabled: false }, - { name: "Saint Martin (French part)", value: "MF", disabled: false }, - { name: "Saint Pierre and Miquelon", value: "PM", disabled: false }, - { name: "Saint Vincent and the Grenadines", value: "VC", disabled: false }, - { name: "Samoa", value: "WS", disabled: false }, - { name: "San Marino", value: "SM", disabled: false }, - { name: "Sao Tome and Principe", value: "ST", disabled: false }, - { name: "Saudi Arabia", value: "SA", disabled: false }, - { name: "Senegal", value: "SN", disabled: false }, - { name: "Serbia", value: "RS", disabled: false }, - { name: "Seychelles", value: "SC", disabled: false }, - { name: "Sierra Leone", value: "SL", disabled: false }, - { name: "Singapore", value: "SG", disabled: false }, - { name: "Sint Maarten (Dutch part)", value: "SX", disabled: false }, - { name: "Slovakia", value: "SK", disabled: false }, - { name: "Slovenia", value: "SI", disabled: false }, - { name: "Solomon Islands", value: "SB", disabled: false }, - { name: "Somalia", value: "SO", disabled: false }, - { name: "South Africa", value: "ZA", disabled: false }, - { name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false }, - { name: "South Sudan", value: "SS", disabled: false }, - { name: "Spain", value: "ES", disabled: false }, - { name: "Sri Lanka", value: "LK", disabled: false }, - { name: "Sudan", value: "SD", disabled: false }, - { name: "Suriname", value: "SR", disabled: false }, - { name: "Svalbard and Jan Mayen", value: "SJ", disabled: false }, - { name: "Swaziland", value: "SZ", disabled: false }, - { name: "Sweden", value: "SE", disabled: false }, - { name: "Switzerland", value: "CH", disabled: false }, - { name: "Syrian Arab Republic", value: "SY", disabled: false }, - { name: "Taiwan", value: "TW", disabled: false }, - { name: "Tajikistan", value: "TJ", disabled: false }, - { name: "Tanzania, United Republic of", value: "TZ", disabled: false }, - { name: "Thailand", value: "TH", disabled: false }, - { name: "Timor-Leste", value: "TL", disabled: false }, - { name: "Togo", value: "TG", disabled: false }, - { name: "Tokelau", value: "TK", disabled: false }, - { name: "Tonga", value: "TO", disabled: false }, - { name: "Trinidad and Tobago", value: "TT", disabled: false }, - { name: "Tunisia", value: "TN", disabled: false }, - { name: "Turkey", value: "TR", disabled: false }, - { name: "Turkmenistan", value: "TM", disabled: false }, - { name: "Turks and Caicos Islands", value: "TC", disabled: false }, - { name: "Tuvalu", value: "TV", disabled: false }, - { name: "Uganda", value: "UG", disabled: false }, - { name: "Ukraine", value: "UA", disabled: false }, - { name: "United Arab Emirates", value: "AE", disabled: false }, - { name: "United States Minor Outlying Islands", value: "UM", disabled: false }, - { name: "Uruguay", value: "UY", disabled: false }, - { name: "Uzbekistan", value: "UZ", disabled: false }, - { name: "Vanuatu", value: "VU", disabled: false }, - { name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false }, - { name: "Viet Nam", value: "VN", disabled: false }, - { name: "Virgin Islands, British", value: "VG", disabled: false }, - { name: "Virgin Islands, U.S.", value: "VI", disabled: false }, - { name: "Wallis and Futuna", value: "WF", disabled: false }, - { name: "Western Sahara", value: "EH", disabled: false }, - { name: "Yemen", value: "YE", disabled: false }, - { name: "Zambia", value: "ZM", disabled: false }, - { name: "Zimbabwe", value: "ZW", disabled: false }, - ]; - - private taxSupportedCountryCodes: string[] = [ - "CN", - "FR", - "DE", - "CA", - "GB", - "AU", - "IN", - "AD", - "AR", - "AT", - "BE", - "BO", - "BR", - "BG", - "CL", - "CO", - "CR", - "HR", - "CY", - "CZ", - "DK", - "DO", - "EC", - "EG", - "SV", - "EE", - "FI", - "GE", - "GR", - "HK", - "HU", - "IS", - "ID", - "IQ", - "IE", - "IL", - "IT", - "JP", - "KE", - "KR", - "LV", - "LI", - "LT", - "LU", - "MY", - "MT", - "MX", - "NL", - "NZ", - "NO", - "PE", - "PH", - "PL", - "PT", - "RO", - "RU", - "SA", - "RS", - "SG", - "SK", - "SI", - "ZA", - "ES", - "SE", - "CH", - "TW", - "TH", - "TR", - "UA", - "AE", - "UY", - "VE", - "VN", - ]; } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 688507099de..149d553696e 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -138,11 +138,13 @@ import { import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { AccountBillingApiService } from "@bitwarden/common/billing/services/account/account-billing-api.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service"; import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; +import { TaxService } from "@bitwarden/common/billing/services/tax.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; @@ -1271,6 +1273,11 @@ const safeProviders: SafeProvider[] = [ useClass: BillingApiService, deps: [ApiServiceAbstraction, LogService, ToastService], }), + safeProvider({ + provide: TaxServiceAbstraction, + useClass: TaxService, + deps: [ApiServiceAbstraction], + }), safeProvider({ provide: BillingAccountProfileStateService, useClass: DefaultBillingAccountProfileStateService, diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts new file mode 100644 index 00000000000..438d3f394e0 --- /dev/null +++ b/libs/common/src/billing/abstractions/tax.service.abstraction.ts @@ -0,0 +1,18 @@ +import { CountryListItem } from "@bitwarden/common/billing/models/domain"; +import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; + +export abstract class TaxServiceAbstraction { + abstract getCountries(): CountryListItem[]; + + abstract isCountrySupported(country: string): Promise; + + abstract previewIndividualInvoice( + request: PreviewIndividualInvoiceRequest, + ): Promise; + + abstract previewOrganizationInvoice( + request: PreviewOrganizationInvoiceRequest, + ): Promise; +} diff --git a/libs/common/src/billing/models/domain/country-list-item.ts b/libs/common/src/billing/models/domain/country-list-item.ts new file mode 100644 index 00000000000..79abb96871c --- /dev/null +++ b/libs/common/src/billing/models/domain/country-list-item.ts @@ -0,0 +1,5 @@ +export type CountryListItem = { + name: string; + value: string; + disabled: boolean; +}; diff --git a/libs/common/src/billing/models/domain/index.ts b/libs/common/src/billing/models/domain/index.ts index 0f53c3e116c..057f6dc4e84 100644 --- a/libs/common/src/billing/models/domain/index.ts +++ b/libs/common/src/billing/models/domain/index.ts @@ -1,2 +1,3 @@ export * from "./bank-account"; +export * from "./country-list-item"; export * from "./tax-information"; diff --git a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts index 838249e2d8c..784d2691629 100644 --- a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts +++ b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts @@ -12,6 +12,10 @@ export class ExpandedTaxInfoUpdateRequest extends TaxInfoUpdateRequest { state: string; static From(taxInformation: TaxInformation): ExpandedTaxInfoUpdateRequest { + if (!taxInformation) { + return null; + } + const request = new ExpandedTaxInfoUpdateRequest(); request.country = taxInformation.country; request.postalCode = taxInformation.postalCode; diff --git a/libs/common/src/billing/models/request/preview-individual-invoice.request.ts b/libs/common/src/billing/models/request/preview-individual-invoice.request.ts new file mode 100644 index 00000000000..f817398c629 --- /dev/null +++ b/libs/common/src/billing/models/request/preview-individual-invoice.request.ts @@ -0,0 +1,28 @@ +// @ts-strict-ignore +export class PreviewIndividualInvoiceRequest { + passwordManager: PasswordManager; + taxInformation: TaxInformation; + + constructor(passwordManager: PasswordManager, taxInformation: TaxInformation) { + this.passwordManager = passwordManager; + this.taxInformation = taxInformation; + } +} + +class PasswordManager { + additionalStorage: number; + + constructor(additionalStorage: number) { + this.additionalStorage = additionalStorage; + } +} + +class TaxInformation { + postalCode: string; + country: string; + + constructor(postalCode: string, country: string) { + this.postalCode = postalCode; + this.country = country; + } +} diff --git a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts new file mode 100644 index 00000000000..365dff5c110 --- /dev/null +++ b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts @@ -0,0 +1,54 @@ +import { PlanType } from "@bitwarden/common/billing/enums"; + +export class PreviewOrganizationInvoiceRequest { + organizationId?: string; + passwordManager: PasswordManager; + secretsManager?: SecretsManager; + taxInformation: TaxInformation; + + constructor( + passwordManager: PasswordManager, + taxInformation: TaxInformation, + organizationId?: string, + secretsManager?: SecretsManager, + ) { + this.organizationId = organizationId; + this.passwordManager = passwordManager; + this.secretsManager = secretsManager; + this.taxInformation = taxInformation; + } +} + +class PasswordManager { + plan: PlanType; + seats: number; + additionalStorage: number; + + constructor(plan: PlanType, seats: number, additionalStorage: number) { + this.plan = plan; + this.seats = seats; + this.additionalStorage = additionalStorage; + } +} + +class SecretsManager { + seats: number; + additionalMachineAccounts: number; + + constructor(seats: number, additionalMachineAccounts: number) { + this.seats = seats; + this.additionalMachineAccounts = additionalMachineAccounts; + } +} + +class TaxInformation { + postalCode: string; + country: string; + taxId: string; + + constructor(postalCode: string, country: string, taxId: string) { + this.postalCode = postalCode; + this.country = country; + this.taxId = taxId; + } +} diff --git a/libs/common/src/billing/models/response/preview-invoice.response.ts b/libs/common/src/billing/models/response/preview-invoice.response.ts new file mode 100644 index 00000000000..c822a569bb3 --- /dev/null +++ b/libs/common/src/billing/models/response/preview-invoice.response.ts @@ -0,0 +1,16 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class PreviewInvoiceResponse extends BaseResponse { + effectiveTaxRate: number; + taxableBaseAmount: number; + taxAmount: number; + totalAmount: number; + + constructor(response: any) { + super(response); + this.effectiveTaxRate = this.getResponseProperty("EffectiveTaxRate"); + this.taxableBaseAmount = this.getResponseProperty("TaxableBaseAmount"); + this.taxAmount = this.getResponseProperty("TaxAmount"); + this.totalAmount = this.getResponseProperty("TotalAmount"); + } +} diff --git a/libs/common/src/billing/models/response/tax-id-types.response.ts b/libs/common/src/billing/models/response/tax-id-types.response.ts new file mode 100644 index 00000000000..0d5cce46c8c --- /dev/null +++ b/libs/common/src/billing/models/response/tax-id-types.response.ts @@ -0,0 +1,28 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +export class TaxIdTypesResponse extends BaseResponse { + taxIdTypes: TaxIdTypeResponse[] = []; + + constructor(response: any) { + super(response); + const taxIdTypes = this.getResponseProperty("TaxIdTypes"); + if (taxIdTypes && taxIdTypes.length) { + this.taxIdTypes = taxIdTypes.map((t: any) => new TaxIdTypeResponse(t)); + } + } +} + +export class TaxIdTypeResponse extends BaseResponse { + code: string; + country: string; + description: string; + example: string; + + constructor(response: any) { + super(response); + this.code = this.getResponseProperty("Code"); + this.country = this.getResponseProperty("Country"); + this.description = this.getResponseProperty("Description"); + this.example = this.getResponseProperty("Example"); + } +} diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts new file mode 100644 index 00000000000..45e57267ec0 --- /dev/null +++ b/libs/common/src/billing/services/tax.service.ts @@ -0,0 +1,303 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { CountryListItem } from "@bitwarden/common/billing/models/domain"; +import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; + +export class TaxService implements TaxServiceAbstraction { + constructor(private apiService: ApiService) {} + + getCountries(): CountryListItem[] { + return [ + { name: "-- Select --", value: "", disabled: false }, + { name: "United States", value: "US", disabled: false }, + { name: "China", value: "CN", disabled: false }, + { name: "France", value: "FR", disabled: false }, + { name: "Germany", value: "DE", disabled: false }, + { name: "Canada", value: "CA", disabled: false }, + { name: "United Kingdom", value: "GB", disabled: false }, + { name: "Australia", value: "AU", disabled: false }, + { name: "India", value: "IN", disabled: false }, + { name: "", value: "-", disabled: true }, + { name: "Afghanistan", value: "AF", disabled: false }, + { name: "Åland Islands", value: "AX", disabled: false }, + { name: "Albania", value: "AL", disabled: false }, + { name: "Algeria", value: "DZ", disabled: false }, + { name: "American Samoa", value: "AS", disabled: false }, + { name: "Andorra", value: "AD", disabled: false }, + { name: "Angola", value: "AO", disabled: false }, + { name: "Anguilla", value: "AI", disabled: false }, + { name: "Antarctica", value: "AQ", disabled: false }, + { name: "Antigua and Barbuda", value: "AG", disabled: false }, + { name: "Argentina", value: "AR", disabled: false }, + { name: "Armenia", value: "AM", disabled: false }, + { name: "Aruba", value: "AW", disabled: false }, + { name: "Austria", value: "AT", disabled: false }, + { name: "Azerbaijan", value: "AZ", disabled: false }, + { name: "Bahamas", value: "BS", disabled: false }, + { name: "Bahrain", value: "BH", disabled: false }, + { name: "Bangladesh", value: "BD", disabled: false }, + { name: "Barbados", value: "BB", disabled: false }, + { name: "Belarus", value: "BY", disabled: false }, + { name: "Belgium", value: "BE", disabled: false }, + { name: "Belize", value: "BZ", disabled: false }, + { name: "Benin", value: "BJ", disabled: false }, + { name: "Bermuda", value: "BM", disabled: false }, + { name: "Bhutan", value: "BT", disabled: false }, + { name: "Bolivia, Plurinational State of", value: "BO", disabled: false }, + { name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false }, + { name: "Bosnia and Herzegovina", value: "BA", disabled: false }, + { name: "Botswana", value: "BW", disabled: false }, + { name: "Bouvet Island", value: "BV", disabled: false }, + { name: "Brazil", value: "BR", disabled: false }, + { name: "British Indian Ocean Territory", value: "IO", disabled: false }, + { name: "Brunei Darussalam", value: "BN", disabled: false }, + { name: "Bulgaria", value: "BG", disabled: false }, + { name: "Burkina Faso", value: "BF", disabled: false }, + { name: "Burundi", value: "BI", disabled: false }, + { name: "Cambodia", value: "KH", disabled: false }, + { name: "Cameroon", value: "CM", disabled: false }, + { name: "Cape Verde", value: "CV", disabled: false }, + { name: "Cayman Islands", value: "KY", disabled: false }, + { name: "Central African Republic", value: "CF", disabled: false }, + { name: "Chad", value: "TD", disabled: false }, + { name: "Chile", value: "CL", disabled: false }, + { name: "Christmas Island", value: "CX", disabled: false }, + { name: "Cocos (Keeling) Islands", value: "CC", disabled: false }, + { name: "Colombia", value: "CO", disabled: false }, + { name: "Comoros", value: "KM", disabled: false }, + { name: "Congo", value: "CG", disabled: false }, + { name: "Congo, the Democratic Republic of the", value: "CD", disabled: false }, + { name: "Cook Islands", value: "CK", disabled: false }, + { name: "Costa Rica", value: "CR", disabled: false }, + { name: "Côte d'Ivoire", value: "CI", disabled: false }, + { name: "Croatia", value: "HR", disabled: false }, + { name: "Cuba", value: "CU", disabled: false }, + { name: "Curaçao", value: "CW", disabled: false }, + { name: "Cyprus", value: "CY", disabled: false }, + { name: "Czech Republic", value: "CZ", disabled: false }, + { name: "Denmark", value: "DK", disabled: false }, + { name: "Djibouti", value: "DJ", disabled: false }, + { name: "Dominica", value: "DM", disabled: false }, + { name: "Dominican Republic", value: "DO", disabled: false }, + { name: "Ecuador", value: "EC", disabled: false }, + { name: "Egypt", value: "EG", disabled: false }, + { name: "El Salvador", value: "SV", disabled: false }, + { name: "Equatorial Guinea", value: "GQ", disabled: false }, + { name: "Eritrea", value: "ER", disabled: false }, + { name: "Estonia", value: "EE", disabled: false }, + { name: "Ethiopia", value: "ET", disabled: false }, + { name: "Falkland Islands (Malvinas)", value: "FK", disabled: false }, + { name: "Faroe Islands", value: "FO", disabled: false }, + { name: "Fiji", value: "FJ", disabled: false }, + { name: "Finland", value: "FI", disabled: false }, + { name: "French Guiana", value: "GF", disabled: false }, + { name: "French Polynesia", value: "PF", disabled: false }, + { name: "French Southern Territories", value: "TF", disabled: false }, + { name: "Gabon", value: "GA", disabled: false }, + { name: "Gambia", value: "GM", disabled: false }, + { name: "Georgia", value: "GE", disabled: false }, + { name: "Ghana", value: "GH", disabled: false }, + { name: "Gibraltar", value: "GI", disabled: false }, + { name: "Greece", value: "GR", disabled: false }, + { name: "Greenland", value: "GL", disabled: false }, + { name: "Grenada", value: "GD", disabled: false }, + { name: "Guadeloupe", value: "GP", disabled: false }, + { name: "Guam", value: "GU", disabled: false }, + { name: "Guatemala", value: "GT", disabled: false }, + { name: "Guernsey", value: "GG", disabled: false }, + { name: "Guinea", value: "GN", disabled: false }, + { name: "Guinea-Bissau", value: "GW", disabled: false }, + { name: "Guyana", value: "GY", disabled: false }, + { name: "Haiti", value: "HT", disabled: false }, + { name: "Heard Island and McDonald Islands", value: "HM", disabled: false }, + { name: "Holy See (Vatican City State)", value: "VA", disabled: false }, + { name: "Honduras", value: "HN", disabled: false }, + { name: "Hong Kong", value: "HK", disabled: false }, + { name: "Hungary", value: "HU", disabled: false }, + { name: "Iceland", value: "IS", disabled: false }, + { name: "Indonesia", value: "ID", disabled: false }, + { name: "Iran, Islamic Republic of", value: "IR", disabled: false }, + { name: "Iraq", value: "IQ", disabled: false }, + { name: "Ireland", value: "IE", disabled: false }, + { name: "Isle of Man", value: "IM", disabled: false }, + { name: "Israel", value: "IL", disabled: false }, + { name: "Italy", value: "IT", disabled: false }, + { name: "Jamaica", value: "JM", disabled: false }, + { name: "Japan", value: "JP", disabled: false }, + { name: "Jersey", value: "JE", disabled: false }, + { name: "Jordan", value: "JO", disabled: false }, + { name: "Kazakhstan", value: "KZ", disabled: false }, + { name: "Kenya", value: "KE", disabled: false }, + { name: "Kiribati", value: "KI", disabled: false }, + { name: "Korea, Democratic People's Republic of", value: "KP", disabled: false }, + { name: "Korea, Republic of", value: "KR", disabled: false }, + { name: "Kuwait", value: "KW", disabled: false }, + { name: "Kyrgyzstan", value: "KG", disabled: false }, + { name: "Lao People's Democratic Republic", value: "LA", disabled: false }, + { name: "Latvia", value: "LV", disabled: false }, + { name: "Lebanon", value: "LB", disabled: false }, + { name: "Lesotho", value: "LS", disabled: false }, + { name: "Liberia", value: "LR", disabled: false }, + { name: "Libya", value: "LY", disabled: false }, + { name: "Liechtenstein", value: "LI", disabled: false }, + { name: "Lithuania", value: "LT", disabled: false }, + { name: "Luxembourg", value: "LU", disabled: false }, + { name: "Macao", value: "MO", disabled: false }, + { name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false }, + { name: "Madagascar", value: "MG", disabled: false }, + { name: "Malawi", value: "MW", disabled: false }, + { name: "Malaysia", value: "MY", disabled: false }, + { name: "Maldives", value: "MV", disabled: false }, + { name: "Mali", value: "ML", disabled: false }, + { name: "Malta", value: "MT", disabled: false }, + { name: "Marshall Islands", value: "MH", disabled: false }, + { name: "Martinique", value: "MQ", disabled: false }, + { name: "Mauritania", value: "MR", disabled: false }, + { name: "Mauritius", value: "MU", disabled: false }, + { name: "Mayotte", value: "YT", disabled: false }, + { name: "Mexico", value: "MX", disabled: false }, + { name: "Micronesia, Federated States of", value: "FM", disabled: false }, + { name: "Moldova, Republic of", value: "MD", disabled: false }, + { name: "Monaco", value: "MC", disabled: false }, + { name: "Mongolia", value: "MN", disabled: false }, + { name: "Montenegro", value: "ME", disabled: false }, + { name: "Montserrat", value: "MS", disabled: false }, + { name: "Morocco", value: "MA", disabled: false }, + { name: "Mozambique", value: "MZ", disabled: false }, + { name: "Myanmar", value: "MM", disabled: false }, + { name: "Namibia", value: "NA", disabled: false }, + { name: "Nauru", value: "NR", disabled: false }, + { name: "Nepal", value: "NP", disabled: false }, + { name: "Netherlands", value: "NL", disabled: false }, + { name: "New Caledonia", value: "NC", disabled: false }, + { name: "New Zealand", value: "NZ", disabled: false }, + { name: "Nicaragua", value: "NI", disabled: false }, + { name: "Niger", value: "NE", disabled: false }, + { name: "Nigeria", value: "NG", disabled: false }, + { name: "Niue", value: "NU", disabled: false }, + { name: "Norfolk Island", value: "NF", disabled: false }, + { name: "Northern Mariana Islands", value: "MP", disabled: false }, + { name: "Norway", value: "NO", disabled: false }, + { name: "Oman", value: "OM", disabled: false }, + { name: "Pakistan", value: "PK", disabled: false }, + { name: "Palau", value: "PW", disabled: false }, + { name: "Palestinian Territory, Occupied", value: "PS", disabled: false }, + { name: "Panama", value: "PA", disabled: false }, + { name: "Papua New Guinea", value: "PG", disabled: false }, + { name: "Paraguay", value: "PY", disabled: false }, + { name: "Peru", value: "PE", disabled: false }, + { name: "Philippines", value: "PH", disabled: false }, + { name: "Pitcairn", value: "PN", disabled: false }, + { name: "Poland", value: "PL", disabled: false }, + { name: "Portugal", value: "PT", disabled: false }, + { name: "Puerto Rico", value: "PR", disabled: false }, + { name: "Qatar", value: "QA", disabled: false }, + { name: "Réunion", value: "RE", disabled: false }, + { name: "Romania", value: "RO", disabled: false }, + { name: "Russian Federation", value: "RU", disabled: false }, + { name: "Rwanda", value: "RW", disabled: false }, + { name: "Saint Barthélemy", value: "BL", disabled: false }, + { name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false }, + { name: "Saint Kitts and Nevis", value: "KN", disabled: false }, + { name: "Saint Lucia", value: "LC", disabled: false }, + { name: "Saint Martin (French part)", value: "MF", disabled: false }, + { name: "Saint Pierre and Miquelon", value: "PM", disabled: false }, + { name: "Saint Vincent and the Grenadines", value: "VC", disabled: false }, + { name: "Samoa", value: "WS", disabled: false }, + { name: "San Marino", value: "SM", disabled: false }, + { name: "Sao Tome and Principe", value: "ST", disabled: false }, + { name: "Saudi Arabia", value: "SA", disabled: false }, + { name: "Senegal", value: "SN", disabled: false }, + { name: "Serbia", value: "RS", disabled: false }, + { name: "Seychelles", value: "SC", disabled: false }, + { name: "Sierra Leone", value: "SL", disabled: false }, + { name: "Singapore", value: "SG", disabled: false }, + { name: "Sint Maarten (Dutch part)", value: "SX", disabled: false }, + { name: "Slovakia", value: "SK", disabled: false }, + { name: "Slovenia", value: "SI", disabled: false }, + { name: "Solomon Islands", value: "SB", disabled: false }, + { name: "Somalia", value: "SO", disabled: false }, + { name: "South Africa", value: "ZA", disabled: false }, + { name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false }, + { name: "South Sudan", value: "SS", disabled: false }, + { name: "Spain", value: "ES", disabled: false }, + { name: "Sri Lanka", value: "LK", disabled: false }, + { name: "Sudan", value: "SD", disabled: false }, + { name: "Suriname", value: "SR", disabled: false }, + { name: "Svalbard and Jan Mayen", value: "SJ", disabled: false }, + { name: "Swaziland", value: "SZ", disabled: false }, + { name: "Sweden", value: "SE", disabled: false }, + { name: "Switzerland", value: "CH", disabled: false }, + { name: "Syrian Arab Republic", value: "SY", disabled: false }, + { name: "Taiwan", value: "TW", disabled: false }, + { name: "Tajikistan", value: "TJ", disabled: false }, + { name: "Tanzania, United Republic of", value: "TZ", disabled: false }, + { name: "Thailand", value: "TH", disabled: false }, + { name: "Timor-Leste", value: "TL", disabled: false }, + { name: "Togo", value: "TG", disabled: false }, + { name: "Tokelau", value: "TK", disabled: false }, + { name: "Tonga", value: "TO", disabled: false }, + { name: "Trinidad and Tobago", value: "TT", disabled: false }, + { name: "Tunisia", value: "TN", disabled: false }, + { name: "Turkey", value: "TR", disabled: false }, + { name: "Turkmenistan", value: "TM", disabled: false }, + { name: "Turks and Caicos Islands", value: "TC", disabled: false }, + { name: "Tuvalu", value: "TV", disabled: false }, + { name: "Uganda", value: "UG", disabled: false }, + { name: "Ukraine", value: "UA", disabled: false }, + { name: "United Arab Emirates", value: "AE", disabled: false }, + { name: "United States Minor Outlying Islands", value: "UM", disabled: false }, + { name: "Uruguay", value: "UY", disabled: false }, + { name: "Uzbekistan", value: "UZ", disabled: false }, + { name: "Vanuatu", value: "VU", disabled: false }, + { name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false }, + { name: "Viet Nam", value: "VN", disabled: false }, + { name: "Virgin Islands, British", value: "VG", disabled: false }, + { name: "Virgin Islands, U.S.", value: "VI", disabled: false }, + { name: "Wallis and Futuna", value: "WF", disabled: false }, + { name: "Western Sahara", value: "EH", disabled: false }, + { name: "Yemen", value: "YE", disabled: false }, + { name: "Zambia", value: "ZM", disabled: false }, + { name: "Zimbabwe", value: "ZW", disabled: false }, + ]; + } + + async isCountrySupported(country: string): Promise { + const response = await this.apiService.send( + "GET", + "/tax/is-country-supported?country=" + country, + null, + true, + true, + ); + return response; + } + + async previewIndividualInvoice( + request: PreviewIndividualInvoiceRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + "/accounts/billing/preview-invoice", + request, + true, + true, + ); + return new PreviewInvoiceResponse(response); + } + + async previewOrganizationInvoice( + request: PreviewOrganizationInvoiceRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + `/invoices/preview-organization`, + request, + true, + true, + ); + return new PreviewInvoiceResponse(response); + } +} From 5a3681655b04b811f67d0c3b0993b1676425a058 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:28:57 -0500 Subject: [PATCH 037/270] [deps] Platform: Update Rust crate libc to v0.2.169 (#12131) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/core/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index e77e66a697c..86c33d34843 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -1508,9 +1508,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 64ed009ceb5..b4fff01e6b0 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -31,7 +31,7 @@ base64 = "=0.22.1" byteorder = "=1.5.0" cbc = { version = "=0.1.2", features = ["alloc"] } homedir = "=0.3.4" -libc = "=0.2.162" +libc = "=0.2.169" pin-project = "=1.1.7" dirs = "=5.0.1" futures = "=0.3.31" From 0ae10f0d9bd40470b8e3a0124f65e1122e412238 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Thu, 2 Jan 2025 14:46:31 -0500 Subject: [PATCH 038/270] fix(safari): make sure to return from bespoke reload clause (#12661) On https://github.com/bitwarden/clients/pull/12326 I refactored and extended a bit of special case logic for process reloads on Safari. This appears to have caused a regression, and I think it's because I didn't return from the function when Safari reloads. This makes it still call `chrome.runtime.reload()`, which makes Safari send an "install" event, which leads to our extension opening it's Getting Started page. This commit returns after the location reload in Safari. This is tricky to test locally as the issue does not appear with sideloaded apps. I'll test it with Testflight! --- apps/browser/src/platform/browser/browser-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index dc71d50d4b1..1d8ff65c17d 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -458,7 +458,7 @@ export class BrowserApi { // and that prompts us to show a new tab, this apparently doesn't happen on sideloaded // extensions and only shows itself production scenarios. See: https://bitwarden.atlassian.net/browse/PM-12298 if (this.isSafariApi) { - self.location.reload(); + return self.location.reload(); } return chrome.runtime.reload(); } From af4311fa21128af1b2c11ae413db8cdab8d0a61f Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:58:34 -0500 Subject: [PATCH 039/270] BRE-534 - Update workflow to parallelize extension builds (#12640) --- .github/workflows/build-browser.yml | 160 +++++++++++++++------------- 1 file changed, 83 insertions(+), 77 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 7740e418e7b..68d1f10a51a 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -114,8 +114,8 @@ jobs: fi - build: - name: Build + build-source: + name: Build browser source runs-on: ubuntu-22.04 needs: - setup @@ -127,7 +127,7 @@ jobs: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -175,15 +175,85 @@ jobs: path: browser-source.zip if-no-files-found: error + + build: + name: Build + runs-on: ubuntu-22.04 + needs: + - setup + - locales-test + - build-source + env: + _BUILD_NUMBER: ${{ needs.setup.outputs.adj_build_number }} + _NODE_VERSION: ${{ needs.setup.outputs.node_version }} + strategy: + matrix: + include: + - name: "chrome" + npm_command: "dist:chrome" + archive_name: "dist-chrome.zip" + artifact_name: "dist-chrome-MV3" + - name: "edge" + npm_command: "dist:edge" + archive_name: "dist-edge.zip" + artifact_name: "dist-edge" + - name: "edge-mv3" + npm_command: "dist:edge:mv3" + archive_name: "dist-edge.zip" + artifact_name: "DO-NOT-USE-FOR-PROD-dist-edge-MV3" + - name: "firefox" + npm_command: "dist:firefox" + archive_name: "dist-firefox.zip" + artifact_name: "dist-firefox" + - name: "firefox-mv3" + npm_command: "dist:firefox:mv3" + archive_name: "dist-firefox.zip" + artifact_name: "DO-NOT-USE-FOR-PROD-dist-firefox-MV3" + - name: "opera" + npm_command: "dist:opera" + archive_name: "dist-opera.zip" + artifact_name: "dist-opera" + - name: "opera-mv3" + npm_command: "dist:opera:mv3" + archive_name: "dist-opera.zip" + artifact_name: "DO-NOT-USE-FOR-PROD-dist-opera-MV3" + steps: + - name: Check out repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up Node + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + node-version: ${{ env._NODE_VERSION }} + + - name: Print environment + run: | + node --version + npm --version + + - name: Download browser source + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: browser-source-${{ env._BUILD_NUMBER }}.zip + + - name: Unzip browser source artifact + run: | + unzip browser-source.zip + rm browser-source.zip + - name: NPM setup run: npm ci working-directory: browser-source/ - - name: Download SDK Artifacts + - name: Download SDK artifacts if: ${{ inputs.sdk_branch != '' }} uses: bitwarden/gh-actions/download-artifacts@main with: - github_token: ${{secrets.GITHUB_TOKEN}} + github_token: ${{ secrets.GITHUB_TOKEN }} workflow: build-wasm-internal.yml workflow_conclusion: success branch: ${{ inputs.sdk_branch }} @@ -195,85 +265,19 @@ jobs: - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: browser-source/ - run: | - npm link ../sdk-internal + run: npm link ../sdk-internal - - name: Build Chrome - run: npm run dist:chrome + - name: Build extension + run: npm run ${{ matrix.npm_command }} working-directory: browser-source/apps/browser - - name: Upload Chrome MV3 artifact + - name: Upload extension artifact uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-chrome.zip + name: ${{ matrix.artifact_name }}-${{ env._BUILD_NUMBER }}.zip + path: browser-source/apps/browser/dist/${{ matrix.archive_name }} if-no-files-found: error - - name: Build Edge - run: npm run dist:edge - working-directory: browser-source/apps/browser - - - name: Upload Edge artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: dist-edge-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-edge.zip - if-no-files-found: error - - - name: Build Edge (MV3) - run: npm run dist:edge:mv3 - working-directory: browser-source/apps/browser - - - name: Upload Edge MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: DO-NOT-USE-FOR-PROD-dist-edge-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-edge.zip - if-no-files-found: error - - - name: Build Firefox - run: npm run dist:firefox - working-directory: browser-source/apps/browser - - - name: Upload Firefox artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: dist-firefox-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-firefox.zip - if-no-files-found: error - - - name: Build Firefox (MV3) - run: npm run dist:firefox:mv3 - working-directory: browser-source/apps/browser - - - name: Upload Firefox MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: DO-NOT-USE-FOR-PROD-dist-firefox-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-firefox.zip - if-no-files-found: error - - - name: Build Opera - run: npm run dist:opera - working-directory: browser-source/apps/browser - - - name: Upload Opera artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: dist-opera-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-opera.zip - if-no-files-found: error - - - name: Build Opera (MV3) - run: npm run dist:opera:mv3 - working-directory: browser-source/apps/browser - - - name: Upload Opera MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: DO-NOT-USE-FOR-PROD-dist-opera-MV3-${{ env._BUILD_NUMBER }}.zip - path: browser-source/apps/browser/dist/dist-opera.zip - if-no-files-found: error build-safari: name: Build Safari @@ -448,6 +452,7 @@ jobs: upload_sources: true upload_translations: false + check-failures: name: Check for failures if: always() @@ -455,6 +460,7 @@ jobs: needs: - setup - locales-test + - build-source - build - build-safari - crowdin-push From 15cc4ff1eb936a6da5ba4a7470f6fa07104970bc Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:37:09 -0600 Subject: [PATCH 040/270] [PM-14461] Update organization state after subscription update (#12222) * Update organization state after subscription update * QA: Fix SM trial seat adjustment --- .../adjust-subscription.component.ts | 18 +++++++++++++++++- .../sm-adjust-subscription.component.ts | 14 +++++++++++++- .../organization-api.service.abstraction.ts | 4 ++-- .../organization/organization-api.service.ts | 14 ++++++++------ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index 73dbb0a0026..0166560007e 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -5,6 +5,8 @@ import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; @@ -34,6 +36,7 @@ export class AdjustSubscription implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private formBuilder: FormBuilder, private toastService: ToastService, + private internalOrganizationService: InternalOrganizationServiceAbstraction, ) {} ngOnInit() { @@ -64,7 +67,20 @@ export class AdjustSubscription implements OnInit, OnDestroy { this.additionalSeatCount, this.adjustSubscriptionForm.value.newMaxSeats, ); - await this.organizationApiService.updatePasswordManagerSeats(this.organizationId, request); + + const response = await this.organizationApiService.updatePasswordManagerSeats( + this.organizationId, + request, + ); + + const organization = await this.internalOrganizationService.get(this.organizationId); + + const organizationData = new OrganizationData(response, { + isMember: organization.isMember, + isProviderUser: organization.isProviderUser, + }); + + await this.internalOrganizationService.upsert(organizationData); this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts index 4a4f309c68b..fc7a188f967 100644 --- a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts @@ -5,6 +5,8 @@ import { FormBuilder, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -104,6 +106,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private toastService: ToastService, + private internalOrganizationService: InternalOrganizationServiceAbstraction, ) {} ngOnInit() { @@ -157,11 +160,20 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest ? this.formGroup.value.maxAutoscaleServiceAccounts : null; - await this.organizationApiService.updateSecretsManagerSubscription( + const response = await this.organizationApiService.updateSecretsManagerSubscription( this.organizationId, request, ); + const organization = await this.internalOrganizationService.get(this.organizationId); + + const organizationData = new OrganizationData(response, { + isMember: organization.isMember, + isProviderUser: organization.isProviderUser, + }); + + await this.internalOrganizationService.upsert(organizationData); + this.toastService.showToast({ variant: "success", title: null, diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 9fd3b358568..0c45c919e95 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -53,11 +53,11 @@ export class OrganizationApiServiceAbstraction { updatePasswordManagerSeats: ( id: string, request: OrganizationSubscriptionUpdateRequest, - ) => Promise; + ) => Promise; updateSecretsManagerSubscription: ( id: string, request: OrganizationSmSubscriptionUpdateRequest, - ) => Promise; + ) => Promise; updateSeats: (id: string, request: SeatRequest) => Promise; updateStorage: (id: string, request: StorageRequest) => Promise; verifyBank: (id: string, request: VerifyBankRequest) => Promise; diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index d62fd49a6a4..b3fd11982b8 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -161,27 +161,29 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction async updatePasswordManagerSeats( id: string, request: OrganizationSubscriptionUpdateRequest, - ): Promise { - return this.apiService.send( + ): Promise { + const r = await this.apiService.send( "POST", "/organizations/" + id + "/subscription", request, true, - false, + true, ); + return new ProfileOrganizationResponse(r); } async updateSecretsManagerSubscription( id: string, request: OrganizationSmSubscriptionUpdateRequest, - ): Promise { - return this.apiService.send( + ): Promise { + const r = await this.apiService.send( "POST", "/organizations/" + id + "/sm-subscription", request, true, - false, + true, ); + return new ProfileOrganizationResponse(r); } async updateSeats(id: string, request: SeatRequest): Promise { From cf9bc7c4555ffe891335774e9d42d19f1c00388d Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:37:48 -0600 Subject: [PATCH 041/270] Vault Strict Typing cleanup (#12512) * remove strict types from `NewDeviceVerificationNotice` - Add null default value for class properties - Enforce that the userId is passed - noticeState$ can return null * remove strict types from `CopyCipherFieldService` - refactor title to be a string rather than null * remove strict types from `PasswordRepromptComponent` - add guard to exit early on submit but also solves removing null/undefined from typing * use bang to ensure required input * remove strict types from `CopyCipherFieldDirective` - add bang for required types - add default values for null types * add bang for constant variables in cipher form stories * remove strict types from `DeleteAttachmentComponent` - add bang for required types - refactor title to be an empty string * fix tests --- libs/vault/src/cipher-form/cipher-form.stories.ts | 10 ++++------ .../delete-attachment.component.spec.ts | 2 +- .../delete-attachment/delete-attachment.component.ts | 8 +++----- .../src/components/copy-cipher-field.directive.ts | 10 ++++------ libs/vault/src/components/org-icon.directive.ts | 4 +--- .../src/components/password-reprompt.component.ts | 8 ++++++-- .../src/services/copy-cipher-field.service.spec.ts | 2 +- libs/vault/src/services/copy-cipher-field.service.ts | 4 +--- .../services/new-device-verification-notice.service.ts | 10 ++++------ 9 files changed, 25 insertions(+), 33 deletions(-) diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 98ccdec360e..1d44e4542bc 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { importProvidersFrom } from "@angular/core"; import { action } from "@storybook/addon-actions"; import { @@ -234,7 +232,7 @@ export const Edit: Story = { config: { ...defaultConfig, mode: "edit", - originalCipher: defaultConfig.originalCipher, + originalCipher: defaultConfig.originalCipher!, }, }, }; @@ -245,7 +243,7 @@ export const PartialEdit: Story = { config: { ...defaultConfig, mode: "partial-edit", - originalCipher: defaultConfig.originalCipher, + originalCipher: defaultConfig.originalCipher!, }, }, }; @@ -256,7 +254,7 @@ export const Clone: Story = { config: { ...defaultConfig, mode: "clone", - originalCipher: defaultConfig.originalCipher, + originalCipher: defaultConfig.originalCipher!, }, }, }; @@ -269,7 +267,7 @@ export const NoPersonalOwnership: Story = { mode: "add", allowPersonalOwnership: false, originalCipher: defaultConfig.originalCipher, - organizations: defaultConfig.organizations, + organizations: defaultConfig.organizations!, }, }, }; diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts index 749093902d7..8e0d4f7a665 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts @@ -98,7 +98,7 @@ describe("DeleteAttachmentComponent", () => { expect(showToast).toHaveBeenCalledWith({ variant: "success", - title: null, + title: "", message: "deletedAttachment", }); }); diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index d5a7039ad70..b1ada907b1d 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; @@ -24,10 +22,10 @@ import { }) export class DeleteAttachmentComponent { /** Id of the cipher associated with the attachment */ - @Input({ required: true }) cipherId: string; + @Input({ required: true }) cipherId!: string; /** The attachment that is can be deleted */ - @Input({ required: true }) attachment: AttachmentView; + @Input({ required: true }) attachment!: AttachmentView; /** Emits when the attachment is successfully deleted */ @Output() onDeletionSuccess = new EventEmitter(); @@ -56,7 +54,7 @@ export class DeleteAttachmentComponent { this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("deletedAttachment"), }); diff --git a/libs/vault/src/components/copy-cipher-field.directive.ts b/libs/vault/src/components/copy-cipher-field.directive.ts index 19bf25a2e2e..1eb96a30449 100644 --- a/libs/vault/src/components/copy-cipher-field.directive.ts +++ b/libs/vault/src/components/copy-cipher-field.directive.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Directive, HostBinding, HostListener, Input, OnChanges, Optional } from "@angular/core"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -28,9 +26,9 @@ export class CopyCipherFieldDirective implements OnChanges { alias: "appCopyField", required: true, }) - action: Exclude; + action!: Exclude; - @Input({ required: true }) cipher: CipherView; + @Input({ required: true }) cipher!: CipherView; constructor( private copyCipherFieldService: CopyCipherFieldService, @@ -52,7 +50,7 @@ export class CopyCipherFieldDirective implements OnChanges { @HostListener("click") async copy() { const value = this.getValueToCopy(); - await this.copyCipherFieldService.copy(value, this.action, this.cipher); + await this.copyCipherFieldService.copy(value ?? "", this.action, this.cipher); } async ngOnChanges() { @@ -69,7 +67,7 @@ export class CopyCipherFieldDirective implements OnChanges { // If the directive is used on a menu item, update the menu item to prevent keyboard navigation if (this.menuItemDirective) { - this.menuItemDirective.disabled = this.disabled; + this.menuItemDirective.disabled = this.disabled ?? false; } } diff --git a/libs/vault/src/components/org-icon.directive.ts b/libs/vault/src/components/org-icon.directive.ts index bcf42503760..69a19b46c65 100644 --- a/libs/vault/src/components/org-icon.directive.ts +++ b/libs/vault/src/components/org-icon.directive.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Directive, ElementRef, HostBinding, Input, Renderer2 } from "@angular/core"; import { ProductTierType } from "@bitwarden/common/billing/enums"; @@ -11,7 +9,7 @@ export type OrgIconSize = "default" | "small" | "large"; selector: "[appOrgIcon]", }) export class OrgIconDirective { - @Input({ required: true }) tierType: ProductTierType; + @Input({ required: true }) tierType!: ProductTierType; @Input() size?: OrgIconSize = "default"; constructor( diff --git a/libs/vault/src/components/password-reprompt.component.ts b/libs/vault/src/components/password-reprompt.component.ts index acedcd79b81..abefac58747 100644 --- a/libs/vault/src/components/password-reprompt.component.ts +++ b/libs/vault/src/components/password-reprompt.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DialogRef } from "@angular/cdk/dialog"; import { Component } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; @@ -51,6 +49,12 @@ export class PasswordRepromptComponent { ) {} submit = async () => { + // Exit early when a master password is not provided. + // The form field required error will be shown to users in these cases. + if (!this.formGroup.value.masterPassword) { + return; + } + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); if (userId == null) { diff --git a/libs/vault/src/services/copy-cipher-field.service.spec.ts b/libs/vault/src/services/copy-cipher-field.service.spec.ts index fa148b0e2e3..48510b2efd9 100644 --- a/libs/vault/src/services/copy-cipher-field.service.spec.ts +++ b/libs/vault/src/services/copy-cipher-field.service.spec.ts @@ -76,7 +76,7 @@ describe("CopyCipherFieldService", () => { expect(toastService.showToast).toHaveBeenCalledWith({ variant: "success", message: "Username copied", - title: null, + title: "", }); expect(i18nService.t).toHaveBeenCalledWith("username"); expect(i18nService.t).toHaveBeenCalledWith("valueCopied", "Username"); diff --git a/libs/vault/src/services/copy-cipher-field.service.ts b/libs/vault/src/services/copy-cipher-field.service.ts index 1b3bb6c0a8c..bfcf3495865 100644 --- a/libs/vault/src/services/copy-cipher-field.service.ts +++ b/libs/vault/src/services/copy-cipher-field.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; @@ -131,7 +129,7 @@ export class CopyCipherFieldService { this.toastService.showToast({ variant: "success", message: this.i18nService.t("valueCopied", this.i18nService.t(action.typeI18nKey)), - title: null, + title: "", }); if (action.event !== undefined) { diff --git a/libs/vault/src/services/new-device-verification-notice.service.ts b/libs/vault/src/services/new-device-verification-notice.service.ts index bb096ff0c2c..a925cf09ec3 100644 --- a/libs/vault/src/services/new-device-verification-notice.service.ts +++ b/libs/vault/src/services/new-device-verification-notice.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Injectable } from "@angular/core"; import { Observable } from "rxjs"; import { Jsonify } from "type-fest"; @@ -17,8 +15,8 @@ import { UserId } from "@bitwarden/common/types/guid"; // If a user dismisses the notice, use "last_dismissal" to wait 7 days before re-prompting // permanent_dismissal will be checked if the user should never see the notice again export class NewDeviceVerificationNotice { - last_dismissal: Date; - permanent_dismissal: boolean; + last_dismissal: Date | null = null; + permanent_dismissal: boolean | null = null; constructor(obj: Partial) { if (obj == null) { @@ -52,12 +50,12 @@ export class NewDeviceVerificationNoticeService { return this.stateProvider.getUser(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY); } - noticeState$(userId: UserId): Observable { + noticeState$(userId: UserId): Observable { return this.noticeState(userId).state$; } async updateNewDeviceVerificationNoticeState( - userId: UserId | null, + userId: UserId, newState: NewDeviceVerificationNotice, ): Promise { await this.noticeState(userId).update(() => { From b9660194be665eb42d96252840148f89c232c056 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:38:29 -0500 Subject: [PATCH 042/270] [deps] Platform: Update tsconfig-paths-webpack-plugin to v4.2.0 (#12136) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ab4e6f1cab..197b7cb0093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -171,7 +171,7 @@ "tailwindcss": "3.4.17", "ts-jest": "29.2.2", "ts-loader": "9.5.1", - "tsconfig-paths-webpack-plugin": "4.1.0", + "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", "typescript": "5.4.2", "typescript-strict-plugin": "^2.4.4", @@ -30565,14 +30565,15 @@ } }, "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", "tsconfig-paths": "^4.1.2" }, "engines": { diff --git a/package.json b/package.json index 513c96c0a2e..907723c3a02 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "tailwindcss": "3.4.17", "ts-jest": "29.2.2", "ts-loader": "9.5.1", - "tsconfig-paths-webpack-plugin": "4.1.0", + "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", "typescript": "5.4.2", "typescript-strict-plugin": "^2.4.4", From 10c8a2101a7b973d9c92e509fe33cfd87c9f34d5 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Thu, 2 Jan 2025 17:16:33 -0500 Subject: [PATCH 043/270] [PM-12049] Remove usage of ActiveUserState from folder service (#11880) * Migrated folder service from using active user state to single user state Added extra test cases for encrypted folder and decrypted folders Updated derived state to use decrypt with key * Update callers in the web * Update callers in the browser * Update callers in libs * Update callers in cli * Fixed test * Fixed folder state test * Fixed test * removed duplicate activeUserId * Added takewhile operator to only make calls when userId is present * Simplified to accept a single user id instead of an observable * Required userid to be passed from notification service * [PM-15635] Folders not working on desktop (#12333) * Added folders memory state definition * added decrypted folders state * Refactored service to remove derived state * removed combinedstate and added clear decrypted folders to methods * Fixed test * Fixed issue with editing folder on the desktop app * Fixed test * Changed state name * fixed ts strict issue * fixed ts strict issue * fixed ts strict issue * removed unnecessasry null encrypteed folder check * Handle null folderdata * [PM-16197] "Items with No Folder" shows as a folder to edit name and delete (#12470) * Force redcryption anytime encryption state changes * Fixed text file * revert changes * create new object with nofolder instead of modifying exisiting object * Fixed failing test * switched to use memory-large-object * Fixed ts sctrict issue --------- Co-authored-by: Matt Bishop Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com> --- .../notification.background.spec.ts | 15 +- .../background/notification.background.ts | 22 +- .../sync/foreground-sync.service.spec.ts | 5 +- .../add-edit-folder-dialog.component.spec.ts | 2 +- .../add-edit-folder-dialog.component.ts | 12 +- .../vault-popup-list-filters.service.spec.ts | 11 +- .../vault-popup-list-filters.service.ts | 117 ++++++----- .../settings/folders-v2.component.spec.ts | 6 +- .../popup/settings/folders-v2.component.ts | 11 +- .../vault/popup/settings/folders.component.ts | 15 +- .../vault/services/vault-filter.service.ts | 3 +- apps/cli/src/commands/edit.command.ts | 20 +- apps/cli/src/commands/get.command.ts | 15 +- apps/cli/src/commands/list.command.ts | 9 +- apps/cli/src/oss-serve-configurator.ts | 2 + apps/cli/src/vault.program.ts | 2 + apps/cli/src/vault/create.command.ts | 18 +- apps/cli/src/vault/delete.command.ts | 11 +- .../emergency-view-dialog.component.spec.ts | 7 + .../migrate-legacy-encryption.component.ts | 2 +- .../bulk-move-dialog.component.ts | 9 +- .../folder-add-edit.component.ts | 6 +- .../services/vault-filter.service.spec.ts | 3 +- .../services/vault-filter.service.ts | 14 +- .../individual-vault/view.component.spec.ts | 4 + .../vault-filter/vault-filter.service.ts | 3 + .../vault/components/add-edit.component.ts | 8 +- .../components/folder-add-edit.component.ts | 19 +- .../src/vault/components/view.component.ts | 8 +- .../services/vault-filter.service.ts | 12 +- .../src/platform/state/state-definitions.ts | 3 + .../src/platform/sync/core-sync.service.ts | 24 ++- libs/common/src/platform/sync/sync.service.ts | 3 +- .../src/services/notifications.service.ts | 6 +- .../vault-timeout.service.spec.ts | 2 +- .../vault-timeout/vault-timeout.service.ts | 2 +- .../folder/folder-api.service.abstraction.ts | 8 +- .../folder/folder.service.abstraction.ts | 28 +-- .../services/folder/folder-api.service.ts | 14 +- .../services/folder/folder.service.spec.ts | 155 ++++++++------ .../vault/services/folder/folder.service.ts | 193 +++++++++++++----- .../services/key-state/folder.state.spec.ts | 70 +++---- .../vault/services/key-state/folder.state.ts | 29 +-- .../src/components/import.component.ts | 10 +- .../individual-vault-export.service.spec.ts | 10 +- .../individual-vault-export.service.ts | 13 +- .../src/services/vault-export.service.spec.ts | 10 +- .../default-cipher-form-config.service.ts | 10 +- .../src/cipher-view/cipher-view.component.ts | 14 +- 49 files changed, 600 insertions(+), 395 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index e043dbfdd2e..37c05a55a3a 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -60,10 +60,18 @@ describe("NotificationBackground", () => { const configService = mock(); const accountService = mock(); + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + beforeEach(() => { activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Locked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; + accountService.activeAccount$ = activeAccountSubject; notificationBackground = new NotificationBackground( autofillService, cipherService, @@ -683,13 +691,6 @@ describe("NotificationBackground", () => { }); describe("saveOrUpdateCredentials", () => { - const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ - id: "testId" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", - }); - let getDecryptedCipherByIdSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; let updatePasswordSpy: jest.SpyInstance; diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 0947ce1e1da..5c6ff3c2c8c 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -83,6 +83,8 @@ export default class NotificationBackground { getWebVaultUrlForNotification: () => this.getWebVaultUrl(), }; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private autofillService: AutofillService, private cipherService: CipherService, @@ -569,9 +571,7 @@ export default class NotificationBackground { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(newCipher, activeUserId); try { @@ -611,10 +611,7 @@ export default class NotificationBackground { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(cipherView, activeUserId); try { // We've only updated the password, no need to broadcast editedCipher message @@ -647,17 +644,15 @@ export default class NotificationBackground { if (Utils.isNullOrWhitespace(folderId) || folderId === "null") { return false; } - - const folders = await firstValueFrom(this.folderService.folderViews$); + const activeUserId = await firstValueFrom(this.activeUserId$); + const folders = await firstValueFrom(this.folderService.folderViews$(activeUserId)); return folders.some((x) => x.id === folderId); } private async getDecryptedCipherById(cipherId: string) { const cipher = await this.cipherService.get(cipherId); if (cipher != null && cipher.type === CipherType.Login) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), @@ -697,7 +692,8 @@ export default class NotificationBackground { * Returns the first value found from the folder service's folderViews$ observable. */ private async getFolderData() { - return await firstValueFrom(this.folderService.folderViews$); + const activeUserId = await firstValueFrom(this.activeUserId$); + return await firstValueFrom(this.folderService.folderViews$(activeUserId)); } private async getWebVaultUrl(): Promise { diff --git a/apps/browser/src/platform/sync/foreground-sync.service.spec.ts b/apps/browser/src/platform/sync/foreground-sync.service.spec.ts index e1e921cc3a3..f5daff93815 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.spec.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.spec.ts @@ -3,7 +3,6 @@ import { Subject } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; @@ -30,12 +29,12 @@ describe("ForegroundSyncService", () => { const cipherService = mock(); const collectionService = mock(); const apiService = mock(); - const accountService = mock(); + const accountService = mockAccountServiceWith(userId); const authService = mock(); const sendService = mock(); const sendApiService = mock(); const messageListener = mock(); - const stateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); + const stateProvider = new FakeStateProvider(accountService); const sut = new ForegroundSyncService( stateService, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts index 4e222a554f7..cbec7903031 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts @@ -171,7 +171,7 @@ describe("AddEditFolderDialogComponent", () => { it("deletes the folder", async () => { await component.deleteFolder(); - expect(deleteFolder).toHaveBeenCalledWith(folderView.id); + expect(deleteFolder).toHaveBeenCalledWith(folderView.id, ""); expect(showToast).toHaveBeenCalledWith({ variant: "success", title: null, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts index 5fbd54d9d78..a50403cea2d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -13,7 +13,7 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -67,6 +67,7 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { name: ["", Validators.required], }); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); private destroyRef = inject(DestroyRef); constructor( @@ -114,10 +115,10 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { this.folder.name = this.folderForm.controls.name.value; try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(this.folder, userKey); - await this.folderApiService.save(folder); + await this.folderApiService.save(folder, activeUserId); this.toastService.showToast({ variant: "success", @@ -144,7 +145,8 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { } try { - await this.folderApiService.delete(this.folder.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.folderApiService.delete(this.folder.id, activeUserId); this.toastService.showToast({ variant: "success", title: null, diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 580514de610..0eb91c6cbe2 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -7,9 +7,12 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateProvider } from "@bitwarden/common/platform/state"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +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"; @@ -32,7 +35,7 @@ describe("VaultPopupListFiltersService", () => { } as unknown as CollectionService; const folderService = { - folderViews$, + folderViews$: () => folderViews$, } as unknown as FolderService; const cipherService = { @@ -60,6 +63,8 @@ describe("VaultPopupListFiltersService", () => { policyAppliesToActiveUser$.next(false); policyService.policyAppliesToActiveUser$.mockClear(); + const accountService = mockAccountServiceWith("userId" as UserId); + collectionService.getAllNested = () => Promise.resolve([]); TestBed.configureTestingModule({ providers: [ @@ -92,6 +97,10 @@ describe("VaultPopupListFiltersService", () => { useValue: { getGlobal: () => ({ state$, update }) }, }, { provide: FormBuilder, useClass: FormBuilder }, + { + provide: AccountService, + useValue: accountService, + }, ], }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index 0ab08d01e46..8455fd587d0 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -20,6 +20,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -102,6 +103,8 @@ export class VaultPopupListFiltersService { map((ciphers) => Object.values(ciphers)), ); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private folderService: FolderService, private cipherService: CipherService, @@ -111,6 +114,7 @@ export class VaultPopupListFiltersService { private formBuilder: FormBuilder, private policyService: PolicyService, private stateProvider: StateProvider, + private accountService: AccountService, ) { this.filterForm.controls.organization.valueChanges .pipe(takeUntilDestroyed()) @@ -264,61 +268,68 @@ export class VaultPopupListFiltersService { /** * Folder array structured to be directly passed to `ChipSelectComponent` */ - folders$: Observable[]> = combineLatest([ - this.filters$.pipe( - distinctUntilChanged( - (previousFilter, currentFilter) => - // Only update the collections when the organizationId filter changes - previousFilter.organization?.id === currentFilter.organization?.id, + folders$: Observable[]> = this.activeUserId$.pipe( + switchMap((userId) => + combineLatest([ + this.filters$.pipe( + distinctUntilChanged( + (previousFilter, currentFilter) => + // Only update the collections when the organizationId filter changes + previousFilter.organization?.id === currentFilter.organization?.id, + ), + ), + this.folderService.folderViews$(userId), + this.cipherViews$, + ]).pipe( + map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { + if (folders.length === 1 && folders[0].id === null) { + // Do not display folder selections when only the "no folder" option is available. + return [filters, [], cipherViews]; + } + + // Sort folders by alphabetic name + folders.sort(Utils.getSortFunction(this.i18nService, "name")); + let arrangedFolders = folders; + + const noFolder = folders.find((f) => f.id === null); + + if (noFolder) { + // Update `name` of the "no folder" option to "Items with no folder" + const updatedNoFolder = { + ...noFolder, + name: this.i18nService.t("itemsWithNoFolder"), + }; + + // Move the "no folder" option to the end of the list + arrangedFolders = [...folders.filter((f) => f.id !== null), updatedNoFolder]; + } + return [filters, arrangedFolders, cipherViews]; + }), + map(([filters, folders, cipherViews]) => { + const organizationId = filters.organization?.id ?? null; + + // When no org or "My vault" is selected, return all folders + if (organizationId === null || organizationId === MY_VAULT_ID) { + return folders; + } + + const orgCiphers = cipherViews.filter((c) => c.organizationId === organizationId); + + // Return only the folders that have ciphers within the filtered organization + return folders.filter((f) => orgCiphers.some((oc) => oc.folderId === f.id)); + }), + map((folders) => { + const nestedFolders = this.getAllFoldersNested(folders); + return new DynamicTreeNode({ + fullList: folders, + nestedList: nestedFolders, + }); + }), + map((folders) => + folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), + ), ), ), - this.folderService.folderViews$, - this.cipherViews$, - ]).pipe( - map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { - if (folders.length === 1 && folders[0].id === null) { - // Do not display folder selections when only the "no folder" option is available. - return [filters, [], cipherViews]; - } - - // Sort folders by alphabetic name - folders.sort(Utils.getSortFunction(this.i18nService, "name")); - let arrangedFolders = folders; - - const noFolder = folders.find((f) => f.id === null); - - if (noFolder) { - // Update `name` of the "no folder" option to "Items with no folder" - noFolder.name = this.i18nService.t("itemsWithNoFolder"); - - // Move the "no folder" option to the end of the list - arrangedFolders = [...folders.filter((f) => f.id !== null), noFolder]; - } - return [filters, arrangedFolders, cipherViews]; - }), - map(([filters, folders, cipherViews]) => { - const organizationId = filters.organization?.id ?? null; - - // When no org or "My vault" is selected, return all folders - if (organizationId === null || organizationId === MY_VAULT_ID) { - return folders; - } - - const orgCiphers = cipherViews.filter((c) => c.organizationId === organizationId); - - // Return only the folders that have ciphers within the filtered organization - return folders.filter((f) => orgCiphers.some((oc) => oc.folderId === f.id)); - }), - map((folders) => { - const nestedFolders = this.getAllFoldersNested(folders); - return new DynamicTreeNode({ - fullList: folders, - nestedList: nestedFolders, - }); - }), - map((folders) => - folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), - ), ); /** diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts index eecad04613e..9c202e26fef 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts @@ -4,10 +4,13 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { DialogService } from "@bitwarden/components"; @@ -52,8 +55,9 @@ describe("FoldersV2Component", () => { { provide: PlatformUtilsService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, { provide: LogService, useValue: mock() }, - { provide: FolderService, useValue: { folderViews$ } }, + { provide: FolderService, useValue: { folderViews$: () => folderViews$ } }, { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }) .overrideComponent(FoldersV2Component, { diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index ce196132f88..b1db949f2ee 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -1,8 +1,10 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { map, Observable } from "rxjs"; +import { filter, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { @@ -45,18 +47,21 @@ export class FoldersV2Component { folders$: Observable; NoFoldersIcon = VaultIcons.NoFolders; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); constructor( private folderService: FolderService, private dialogService: DialogService, + private accountService: AccountService, ) { - this.folders$ = this.folderService.folderViews$.pipe( + this.folders$ = this.activeUserId$.pipe( + filter((userId): userId is UserId => userId !== null), + switchMap((userId) => this.folderService.folderViews$(userId)), map((folders) => { // Remove the last folder, which is the "no folder" option folder if (folders.length > 0) { return folders.slice(0, folders.length - 1); } - return folders; }), ); diff --git a/apps/browser/src/vault/popup/settings/folders.component.ts b/apps/browser/src/vault/popup/settings/folders.component.ts index edf7fe939e8..1e3f182b43d 100644 --- a/apps/browser/src/vault/popup/settings/folders.component.ts +++ b/apps/browser/src/vault/popup/settings/folders.component.ts @@ -1,7 +1,9 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { map, Observable } from "rxjs"; +import { filter, map, Observable, switchMap } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; @@ -12,16 +14,21 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; export class FoldersComponent { folders$: Observable; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private folderService: FolderService, private router: Router, + private accountService: AccountService, ) { - this.folders$ = this.folderService.folderViews$.pipe( + this.folders$ = this.activeUserId$.pipe( + filter((userId): userId is UserId => userId != null), + switchMap((userId) => this.folderService.folderViews$(userId)), map((folders) => { + // Remove the last folder, which is the "no folder" option folder if (folders.length > 0) { - folders = folders.slice(0, folders.length - 1); + return folders.slice(0, folders.length - 1); } - return folders; }), ); diff --git a/apps/browser/src/vault/services/vault-filter.service.ts b/apps/browser/src/vault/services/vault-filter.service.ts index ba1d11f5d27..305c7de487b 100644 --- a/apps/browser/src/vault/services/vault-filter.service.ts +++ b/apps/browser/src/vault/services/vault-filter.service.ts @@ -24,7 +24,7 @@ export class VaultFilterService extends BaseVaultFilterService { collectionService: CollectionService, policyService: PolicyService, stateProvider: StateProvider, - private accountService: AccountService, + accountService: AccountService, ) { super( organizationService, @@ -33,6 +33,7 @@ export class VaultFilterService extends BaseVaultFilterService { collectionService, policyService, stateProvider, + accountService, ); this.vaultFilter.myVaultOnly = false; this.vaultFilter.selectedOrganizationId = null; diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index da7c98a35d6..dd99d03b086 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -24,6 +24,8 @@ import { CipherResponse } from "../vault/models/cipher.response"; import { FolderResponse } from "../vault/models/folder.response"; export class EditCommand { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private folderService: FolderService, @@ -121,12 +123,12 @@ export class EditCommand { cipher.collectionIds = req; try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher); const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + await this.cipherService.getKeyForCipherKeyDecryption( + updatedCipher, + await firstValueFrom(this.activeUserId$), + ), ); const res = new CipherResponse(decCipher); return Response.success(res); @@ -136,7 +138,8 @@ export class EditCommand { } private async editFolder(id: string, req: FolderExport) { - const folder = await this.folderService.getFromState(id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); } @@ -144,12 +147,11 @@ export class EditCommand { let folderView = await folder.decrypt(); folderView = FolderExport.toView(req, folderView); - const activeUserId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const encFolder = await this.folderService.encrypt(folderView, userKey); try { - await this.folderApiService.save(encFolder); - const updatedFolder = await this.folderService.get(folder.id); + await this.folderApiService.save(encFolder, activeUserId); + const updatedFolder = await this.folderService.get(folder.id, activeUserId); const decFolder = await updatedFolder.decrypt(); const res = new FolderResponse(decFolder); return Response.success(res); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 1a835befd01..7c3cc7caa9f 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -51,6 +51,8 @@ import { FolderResponse } from "../vault/models/folder.response"; import { DownloadCommand } from "./download.command"; export class GetCommand extends DownloadCommand { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private folderService: FolderService, @@ -113,10 +115,8 @@ export class GetCommand extends DownloadCommand { let decCipher: CipherView = null; if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); if (cipher != null) { + const activeUserId = await firstValueFrom(this.activeUserId$); decCipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -383,13 +383,14 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; + const activeUserId = await firstValueFrom(this.activeUserId$); if (Utils.isGuid(id)) { - const folder = await this.folderService.getFromState(id); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { decFolder = await folder.decrypt(); } } else if (id.trim() !== "") { - let folders = await this.folderService.getAllDecryptedFromState(); + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); folders = CliUtils.searchFolders(folders, id); if (folders.length > 1) { return Response.multipleResults(folders.map((f) => f.id)); @@ -551,9 +552,7 @@ export class GetCommand extends DownloadCommand { private async getFingerprint(id: string) { let fingerprint: string[] = null; if (id === "me") { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); } else if (Utils.isGuid(id)) { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 9cb36f71496..92da86b696a 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { OrganizationUserApiService, @@ -12,6 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -38,6 +39,7 @@ export class ListCommand { private organizationUserApiService: OrganizationUserApiService, private apiService: ApiService, private eventCollectionService: EventCollectionService, + private accountService: AccountService, ) {} async run(object: string, cmdOptions: Record): Promise { @@ -135,7 +137,10 @@ export class ListCommand { } private async listFolders(options: Options) { - let folders = await this.folderService.getAllDecryptedFromState(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); if (options.search != null && options.search.trim() !== "") { folders = CliUtils.searchFolders(folders, options.search); diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 71c372ef301..9bd3a2bee5f 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -76,6 +76,7 @@ export class OssServeConfigurator { this.serviceContainer.organizationUserApiService, this.serviceContainer.apiService, this.serviceContainer.eventCollectionService, + this.serviceContainer.accountService, ); this.createCommand = new CreateCommand( this.serviceContainer.cipherService, @@ -115,6 +116,7 @@ export class OssServeConfigurator { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.cipherAuthorizationService, + this.serviceContainer.accountService, ); this.confirmCommand = new ConfirmCommand( this.serviceContainer.apiService, diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 1cdf23bcd89..3eb0e68de09 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -113,6 +113,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.organizationUserApiService, this.serviceContainer.apiService, this.serviceContainer.eventCollectionService, + this.serviceContainer.accountService, ); const response = await command.run(object, cmd); @@ -321,6 +322,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, this.serviceContainer.cipherAuthorizationService, + this.serviceContainer.accountService, ); const response = await command.run(object, id, cmd); this.processResponse(response); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index aa01ec3c491..47e91cb55ff 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -30,6 +30,8 @@ import { CipherResponse } from "./models/cipher.response"; import { FolderResponse } from "./models/folder.response"; export class CreateCommand { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private folderService: FolderService, @@ -86,9 +88,7 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); @@ -152,9 +152,7 @@ export class CreateCommand { } try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const updatedCipher = await this.cipherService.saveAttachmentRawWithServer( cipher, fileName, @@ -171,12 +169,12 @@ export class CreateCommand { } private async createFolder(req: FolderExport) { - const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { - await this.folderApiService.save(folder); - const newFolder = await this.folderService.get(folder.id); + await this.folderApiService.save(folder, activeUserId); + const newFolder = await this.folderService.get(folder.id, activeUserId); const decFolder = await newFolder.decrypt(); const res = new FolderResponse(decFolder); return Response.success(res); diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 7f79c89b17c..6b66b8bc7bb 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -1,6 +1,7 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -19,6 +20,7 @@ export class DeleteCommand { private folderApiService: FolderApiServiceAbstraction, private accountProfileService: BillingAccountProfileStateService, private cipherAuthorizationService: CipherAuthorizationService, + private accountService: AccountService, ) {} async run(object: string, id: string, cmdOptions: Record): Promise { @@ -103,13 +105,16 @@ export class DeleteCommand { } private async deleteFolder(id: string) { - const folder = await this.folderService.getFromState(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); } try { - await this.folderApiService.delete(id); + await this.folderApiService.delete(id, activeUserId); return Response.success(); } catch (e) { return Response.error(e); diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index 341e44f643b..0ad7eef81be 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -6,7 +6,11 @@ import { mock } from "jest-mock-extended"; import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -29,6 +33,8 @@ describe("EmergencyViewDialogComponent", () => { card: {}, } as CipherView; + const accountService: FakeAccountService = mockAccountServiceWith(Utils.newGuid() as UserId); + beforeEach(async () => { open.mockClear(); close.mockClear(); @@ -43,6 +49,7 @@ describe("EmergencyViewDialogComponent", () => { { provide: DialogService, useValue: { open } }, { provide: DialogRef, useValue: { close } }, { provide: DIALOG_DATA, useValue: { cipher: mockCipher } }, + { provide: AccountService, useValue: accountService }, ], }).compileComponents(); diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts index bb5a1c511c6..2361293fe80 100644 --- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts +++ b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts @@ -83,7 +83,7 @@ export class MigrateFromLegacyEncryptionComponent { }); if (deleteFolders) { - await this.folderApiService.deleteAll(); + await this.folderApiService.deleteAll(activeUser.id); await this.syncService.fullSync(true, true); await this.submit(); return; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index d68e3b9d732..b7f99fb7b44 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -3,8 +3,9 @@ import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { firstValueFrom, Observable } from "rxjs"; +import { firstValueFrom, map, Observable } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -47,6 +48,8 @@ export class BulkMoveDialogComponent implements OnInit { }); folders$: Observable; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( @Inject(DIALOG_DATA) params: BulkMoveDialogParams, private dialogRef: DialogRef, @@ -55,12 +58,14 @@ export class BulkMoveDialogComponent implements OnInit { private i18nService: I18nService, private folderService: FolderService, private formBuilder: FormBuilder, + private accountService: AccountService, ) { this.cipherIds = params.cipherIds ?? []; } async ngOnInit() { - this.folders$ = this.folderService.folderViews$; + const activeUserId = await firstValueFrom(this.activeUserId$); + this.folders$ = this.folderService.folderViews$(activeUserId); this.formGroup.patchValue({ folderId: (await firstValueFrom(this.folders$))[0].id, }); diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 4d181a0510d..88af1ef601b 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -61,7 +61,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { } try { - await this.folderApiService.delete(this.folder.id); + await this.folderApiService.delete(this.folder.id, await firstValueFrom(this.activeUserId$)); this.toastService.showToast({ variant: "success", title: null, @@ -82,10 +82,10 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { } try { - const activeAccountId = (await firstValueFrom(this.accountSerivce.activeAccount$)).id; + const activeAccountId = await firstValueFrom(this.activeUserId$); const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId); const folder = await this.folderService.encrypt(this.folder, userKey); - this.formPromise = this.folderApiService.save(folder); + this.formPromise = this.folderApiService.save(folder, activeAccountId); await this.formPromise; this.platformUtilsService.showToast( "success", 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 0386a20adbb..47003d51cae 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 @@ -63,7 +63,7 @@ describe("vault filter service", () => { singleOrgPolicy = new ReplaySubject(1); organizationService.memberOrganizations$ = organizations; - folderService.folderViews$ = folderViews; + folderService.folderViews$.mockReturnValue(folderViews); collectionService.decryptedCollections$ = collectionViews; policyService.policyAppliesToActiveUser$ .calledWith(PolicyType.PersonalOwnership) @@ -81,6 +81,7 @@ describe("vault filter service", () => { i18nService, stateProvider, collectionService, + accountService, ); collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS); }); 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 c4ac3dc2d70..97b44132e60 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 @@ -21,6 +21,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -45,6 +46,8 @@ const NestingDelimiter = "/"; @Injectable() export class VaultFilterService implements VaultFilterServiceAbstraction { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + organizationTree$: Observable> = combineLatest([ this.organizationService.memberOrganizations$, this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg), @@ -57,8 +60,14 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { protected _organizationFilter = new BehaviorSubject(null); - filteredFolders$: Observable = this.folderService.folderViews$.pipe( - combineLatestWith(this.cipherService.cipherViews$, this._organizationFilter), + filteredFolders$: Observable = this.activeUserId$.pipe( + switchMap((userId) => + combineLatest([ + this.folderService.folderViews$(userId), + this.cipherService.cipherViews$, + this._organizationFilter, + ]), + ), switchMap(([folders, ciphers, org]) => { return this.filterFolders(folders, ciphers, org); }), @@ -95,6 +104,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { protected i18nService: I18nService, protected stateProvider: StateProvider, protected collectionService: CollectionService, + protected accountService: AccountService, ) {} async getCollectionNodeFromTree(id: string) { diff --git a/apps/web/src/app/vault/individual-vault/view.component.spec.ts b/apps/web/src/app/vault/individual-vault/view.component.spec.ts index b26c55d46e8..bde9f564c4a 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.spec.ts @@ -5,11 +5,14 @@ import { mock } from "jest-mock-extended"; import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -63,6 +66,7 @@ describe("ViewComponent", () => { useValue: mock(), }, { provide: ConfigService, useValue: mock() }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, { provide: CipherAuthorizationService, useValue: { diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts index c4ac9d73df7..e2d713649f5 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts +++ b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts @@ -4,6 +4,7 @@ import { map, Observable, ReplaySubject, Subject } from "rxjs"; import { CollectionAdminView, CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -32,6 +33,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest i18nService: I18nService, stateProvider: StateProvider, collectionService: CollectionService, + accountService: AccountService, ) { super( organizationService, @@ -41,6 +43,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest i18nService, stateProvider, collectionService, + accountService, ); } diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 3a9d0289971..4bd3e9710fb 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -102,6 +102,8 @@ export class AddEditComponent implements OnInit, OnDestroy { private personalOwnershipPolicyAppliesToActiveUser: boolean; private previousCipherId: string; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); const creationDate = this.datePipe.transform( @@ -259,12 +261,10 @@ export class AddEditComponent implements OnInit, OnDestroy { const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(); + const activeUserId = await firstValueFrom(this.activeUserId$); if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -323,7 +323,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.login.fido2Credentials = null; } - this.folders$ = this.folderService.folderViews$; + this.folders$ = this.folderService.folderViews$(activeUserId); if (this.editMode && this.previousCipherId !== this.cipherId) { void this.eventCollectionService.collectMany(EventType.Cipher_ClientViewed, [this.cipher]); diff --git a/libs/angular/src/vault/components/folder-add-edit.component.ts b/libs/angular/src/vault/components/folder-add-edit.component.ts index 6d9ae53cc66..205733ba48d 100644 --- a/libs/angular/src/vault/components/folder-add-edit.component.ts +++ b/libs/angular/src/vault/components/folder-add-edit.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Validators, FormBuilder } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -27,6 +27,8 @@ export class FolderAddEditComponent implements OnInit { deletePromise: Promise; protected componentName = ""; + protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + formGroup = this.formBuilder.group({ name: ["", [Validators.required]], }); @@ -59,10 +61,10 @@ export class FolderAddEditComponent implements OnInit { } try { - const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(this.folder, userKey); - this.formPromise = this.folderApiService.save(folder); + this.formPromise = this.folderApiService.save(folder, activeUserId); await this.formPromise; this.platformUtilsService.showToast( "success", @@ -90,7 +92,8 @@ export class FolderAddEditComponent implements OnInit { } try { - this.deletePromise = this.folderApiService.delete(this.folder.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.deletePromise = this.folderApiService.delete(this.folder.id, activeUserId); await this.deletePromise; this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder")); this.onDeletedFolder.emit(this.folder); @@ -107,8 +110,10 @@ export class FolderAddEditComponent implements OnInit { if (this.editMode) { this.editMode = true; this.title = this.i18nService.t("editFolder"); - const folder = await this.folderService.get(this.folderId); - this.folder = await folder.decrypt(); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.folder = await firstValueFrom( + this.folderService.getDecrypted$(this.folderId, activeUserId), + ); } else { this.title = this.i18nService.t("addFolder"); } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index a6b9a571730..6bea4cd6150 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -79,6 +79,8 @@ export class ViewComponent implements OnDestroy, OnInit { private previousCipherId: string; private passwordReprompted = false; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); const creationDate = this.datePipe.transform( @@ -141,9 +143,7 @@ export class ViewComponent implements OnDestroy, OnInit { this.cleanUp(); const cipher = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -158,7 +158,7 @@ export class ViewComponent implements OnDestroy, OnInit { if (this.cipher.folderId) { this.folder = await ( - await firstValueFrom(this.folderService.folderViews$) + await firstValueFrom(this.folderService.folderViews$(activeUserId)) ).find((f) => f.id == this.cipher.folderId); } diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index a2b624876be..dd0b49f356a 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Injectable } from "@angular/core"; -import { firstValueFrom, from, map, mergeMap, Observable } from "rxjs"; +import { firstValueFrom, from, map, mergeMap, Observable, switchMap } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { @@ -11,6 +11,7 @@ import { import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -32,6 +33,8 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti private readonly collapsedGroupings$: Observable> = this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c))); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( protected organizationService: OrganizationService, protected folderService: FolderService, @@ -39,6 +42,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti protected collectionService: CollectionService, protected policyService: PolicyService, protected stateProvider: StateProvider, + protected accountService: AccountService, ) {} async storeCollapsedFilterNodes(collapsedFilterNodes: Set): Promise { @@ -81,7 +85,8 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti }); }; - return this.folderService.folderViews$.pipe( + return this.activeUserId$.pipe( + switchMap((userId) => this.folderService.folderViews$(userId)), mergeMap((folders) => from(transformation(folders))), ); } @@ -126,8 +131,9 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti } async getFolderNested(id: string): Promise> { + const activeUserId = await firstValueFrom(this.activeUserId$); const folders = await this.getAllFoldersNested( - await firstValueFrom(this.folderService.folderViews$), + await firstValueFrom(this.folderService.folderViews$(activeUserId)), ); return ServiceUtils.getTreeNodeObjectFromList(folders, id) as TreeNode; } diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index ce9eb5a15c0..1ed5227cb13 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -150,6 +150,9 @@ export const COLLECTION_DATA = new StateDefinition("collection", "disk", { web: "memory", }); export const FOLDER_DISK = new StateDefinition("folder", "disk", { web: "memory" }); +export const FOLDER_MEMORY = new StateDefinition("decryptedFolders", "memory", { + browser: "memory-large-object", +}); export const VAULT_FILTER_DISK = new StateDefinition("vaultFilter", "disk", { web: "disk-local", }); diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index 180767ccd16..cfa9030c9de 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -85,18 +85,25 @@ export abstract class CoreSyncService implements SyncService { await this.stateProvider.getUser(userId, LAST_SYNC_DATE).update(() => date); } - async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise { + async syncUpsertFolder( + notification: SyncFolderNotification, + isEdit: boolean, + userId: UserId, + ): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + + if (authStatus >= AuthenticationStatus.Locked) { try { - const localFolder = await this.folderService.get(notification.id); + const localFolder = await this.folderService.get(notification.id, userId); if ( (!isEdit && localFolder == null) || (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate) ) { const remoteFolder = await this.folderApiService.get(notification.id); if (remoteFolder != null) { - await this.folderService.upsert(new FolderData(remoteFolder)); + await this.folderService.upsert(new FolderData(remoteFolder), userId); this.messageSender.send("syncedUpsertedFolder", { folderId: notification.id }); return this.syncCompleted(true); } @@ -108,10 +115,13 @@ export abstract class CoreSyncService implements SyncService { return this.syncCompleted(false); } - async syncDeleteFolder(notification: SyncFolderNotification): Promise { + async syncDeleteFolder(notification: SyncFolderNotification, userId: UserId): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - await this.folderService.delete(notification.id); + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + + if (authStatus >= AuthenticationStatus.Locked) { + await this.folderService.delete(notification.id, userId); this.messageSender.send("syncedDeletedFolder", { folderId: notification.id }); this.syncCompleted(true); return true; diff --git a/libs/common/src/platform/sync/sync.service.ts b/libs/common/src/platform/sync/sync.service.ts index 733b7beaff5..6763e01cab7 100644 --- a/libs/common/src/platform/sync/sync.service.ts +++ b/libs/common/src/platform/sync/sync.service.ts @@ -56,8 +56,9 @@ export abstract class SyncService { abstract syncUpsertFolder( notification: SyncFolderNotification, isEdit: boolean, + userId: UserId, ): Promise; - abstract syncDeleteFolder(notification: SyncFolderNotification): Promise; + abstract syncDeleteFolder(notification: SyncFolderNotification, userId: UserId): Promise; abstract syncUpsertCipher( notification: SyncCipherNotification, isEdit: boolean, diff --git a/libs/common/src/services/notifications.service.ts b/libs/common/src/services/notifications.service.ts index 6f7c5c9f262..4a14332af8a 100644 --- a/libs/common/src/services/notifications.service.ts +++ b/libs/common/src/services/notifications.service.ts @@ -168,10 +168,14 @@ export class NotificationsService implements NotificationsServiceAbstraction { await this.syncService.syncUpsertFolder( notification.payload as SyncFolderNotification, notification.type === NotificationType.SyncFolderUpdate, + payloadUserId, ); break; case NotificationType.SyncFolderDelete: - await this.syncService.syncDeleteFolder(notification.payload as SyncFolderNotification); + await this.syncService.syncDeleteFolder( + notification.payload as SyncFolderNotification, + payloadUserId, + ); break; case NotificationType.SyncVault: case NotificationType.SyncCiphers: diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts index 71341a98a62..1350010f849 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts @@ -334,7 +334,7 @@ describe("VaultTimeoutService", () => { // Active users should have additional steps ran expect(searchService.clearIndex).toHaveBeenCalled(); - expect(folderService.clearCache).toHaveBeenCalled(); + expect(folderService.clearDecryptedFolderState).toHaveBeenCalled(); expectUserToHaveLoggedOut("3"); // They have chosen logout as their action and it's available, log them out expectUserToHaveLoggedOut("4"); // They may have had lock as their chosen action but it's not available to them so logout diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index f6ad0f17e9a..f465174bf40 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -135,10 +135,10 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { if (userId == null || userId === currentUserId) { await this.searchService.clearIndex(); - await this.folderService.clearCache(); await this.collectionService.clearActiveUserCache(); } + await this.folderService.clearDecryptedFolderState(userId); await this.masterPasswordService.clearMasterKey(lockingUserId); await this.stateService.setUserKeyAutoUnlock(null, { userId: lockingUserId }); diff --git a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts index a732acfa991..859f2183edb 100644 --- a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts @@ -1,11 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { UserId } from "@bitwarden/common/types/guid"; + import { Folder } from "../../models/domain/folder"; import { FolderResponse } from "../../models/response/folder.response"; export class FolderApiServiceAbstraction { - save: (folder: Folder) => Promise; - delete: (id: string) => Promise; + save: (folder: Folder, userId: UserId) => Promise; + delete: (id: string, userId: UserId) => Promise; get: (id: string) => Promise; - deleteAll: () => Promise; + deleteAll: (userId: UserId) => Promise; } diff --git a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts index c5722e0f57b..b7241e3ae37 100644 --- a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts @@ -13,23 +13,27 @@ import { FolderWithIdRequest } from "../../models/request/folder-with-id.request import { FolderView } from "../../models/view/folder.view"; export abstract class FolderService implements UserKeyRotationDataProvider { - folders$: Observable; - folderViews$: Observable; + folders$: (userId: UserId) => Observable; + folderViews$: (userId: UserId) => Observable; - clearCache: () => Promise; + clearDecryptedFolderState: (userId: UserId) => Promise; encrypt: (model: FolderView, key: SymmetricCryptoKey) => Promise; - get: (id: string) => Promise; - getDecrypted$: (id: string) => Observable; - getAllFromState: () => Promise; + get: (id: string, userId: UserId) => Promise; + getDecrypted$: (id: string, userId: UserId) => Observable; + /** + * @deprecated Use firstValueFrom(folders$) directly instead + * @param userId The user id + * @returns Promise of folders array + */ + getAllFromState: (userId: UserId) => Promise; /** * @deprecated Only use in CLI! */ - getFromState: (id: string) => Promise; + getFromState: (id: string, userId: UserId) => Promise; /** * @deprecated Only use in CLI! */ - getAllDecryptedFromState: () => Promise; - decryptFolders: (folders: Folder[]) => Promise; + getAllDecryptedFromState: (userId: UserId) => Promise; /** * Returns user folders re-encrypted with the new user key. * @param originalUserKey the original user key @@ -46,8 +50,8 @@ export abstract class FolderService implements UserKeyRotationDataProvider Promise; + upsert: (folder: FolderData | FolderData[], userId: UserId) => Promise; replace: (folders: { [id: string]: FolderData }, userId: UserId) => Promise; - clear: (userId?: string) => Promise; - delete: (id: string | string[]) => Promise; + clear: (userId: UserId) => Promise; + delete: (id: string | string[], userId: UserId) => Promise; } diff --git a/libs/common/src/vault/services/folder/folder-api.service.ts b/libs/common/src/vault/services/folder/folder-api.service.ts index e46df37c176..24831393668 100644 --- a/libs/common/src/vault/services/folder/folder-api.service.ts +++ b/libs/common/src/vault/services/folder/folder-api.service.ts @@ -1,3 +1,5 @@ +import { UserId } from "@bitwarden/common/types/guid"; + import { ApiService } from "../../../abstractions/api.service"; import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; @@ -12,7 +14,7 @@ export class FolderApiService implements FolderApiServiceAbstraction { private apiService: ApiService, ) {} - async save(folder: Folder): Promise { + async save(folder: Folder, userId: UserId): Promise { const request = new FolderRequest(folder); let response: FolderResponse; @@ -24,17 +26,17 @@ export class FolderApiService implements FolderApiServiceAbstraction { } const data = new FolderData(response); - await this.folderService.upsert(data); + await this.folderService.upsert(data, userId); } - async delete(id: string): Promise { + async delete(id: string, userId: UserId): Promise { await this.deleteFolder(id); - await this.folderService.delete(id); + await this.folderService.delete(id, userId); } - async deleteAll(): Promise { + async deleteAll(userId: UserId): Promise { await this.apiService.send("DELETE", "/folders/all", null, true, false); - await this.folderService.clear(); + await this.folderService.clear(userId); } async get(id: string): Promise { diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 193d0e85e61..612cd83d99b 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,10 +1,10 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { makeStaticByteArray } from "../../../../spec"; +import { makeEncString } from "../../../../spec"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; -import { FakeActiveUserState } from "../../../../spec/fake-state"; +import { FakeSingleUserState } from "../../../../spec/fake-state"; import { FakeStateProvider } from "../../../../spec/fake-state-provider"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; @@ -17,7 +17,7 @@ import { CipherService } from "../../abstractions/cipher.service"; import { FolderData } from "../../models/data/folder.data"; import { FolderView } from "../../models/view/folder.view"; import { FolderService } from "../../services/folder/folder.service"; -import { FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state"; +import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state"; describe("Folder Service", () => { let folderService: FolderService; @@ -30,7 +30,7 @@ describe("Folder Service", () => { const mockUserId = Utils.newGuid() as UserId; let accountService: FakeAccountService; - let folderState: FakeActiveUserState>; + let folderState: FakeSingleUserState>; beforeEach(() => { keyService = mock(); @@ -42,11 +42,9 @@ describe("Folder Service", () => { stateProvider = new FakeStateProvider(accountService); i18nService.collator = new Intl.Collator("en"); + i18nService.t.mockReturnValue("No Folder"); - keyService.hasUserKey.mockResolvedValue(true); - keyService.getUserKeyWithLegacySupport.mockResolvedValue( - new SymmetricCryptoKey(makeStaticByteArray(32)) as UserKey, - ); + keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); encryptService.decryptToUtf8.mockResolvedValue("DEC"); folderService = new FolderService( @@ -57,10 +55,53 @@ describe("Folder Service", () => { stateProvider, ); - folderState = stateProvider.activeUser.getFake(FOLDER_ENCRYPTED_FOLDERS); + folderState = stateProvider.singleUser.getFake(mockUserId, FOLDER_ENCRYPTED_FOLDERS); // Initial state - folderState.nextState({ "1": folderData("1", "test") }); + folderState.nextState({ "1": folderData("1") }); + }); + + describe("folders$", () => { + it("emits encrypted folders from state", async () => { + const folder1 = folderData("1"); + const folder2 = folderData("2"); + + await stateProvider.setUserState( + FOLDER_ENCRYPTED_FOLDERS, + Object.fromEntries([folder1, folder2].map((f) => [f.id, f])), + mockUserId, + ); + + const result = await firstValueFrom(folderService.folders$(mockUserId)); + + expect(result.length).toBe(2); + expect(result).toIncludeAllPartialMembers([ + { id: "1", name: makeEncString("ENC_STRING_1") }, + { id: "2", name: makeEncString("ENC_STRING_2") }, + ]); + }); + }); + + describe("folderView$", () => { + it("emits decrypted folders from state", async () => { + const folder1 = folderData("1"); + const folder2 = folderData("2"); + + await stateProvider.setUserState( + FOLDER_ENCRYPTED_FOLDERS, + Object.fromEntries([folder1, folder2].map((f) => [f.id, f])), + mockUserId, + ); + + const result = await firstValueFrom(folderService.folderViews$(mockUserId)); + + expect(result.length).toBe(3); + expect(result).toIncludeAllPartialMembers([ + { id: "1", name: "DEC" }, + { id: "2", name: "DEC" }, + { name: "No Folder" }, + ]); + }); }); it("encrypt", async () => { @@ -83,105 +124,83 @@ describe("Folder Service", () => { describe("get", () => { it("exists", async () => { - const result = await folderService.get("1"); + const result = await folderService.get("1", mockUserId); expect(result).toEqual({ id: "1", - name: { - encryptedString: "test", - encryptionType: 0, - }, + name: makeEncString("ENC_STRING_" + 1), revisionDate: null, }); }); it("not exists", async () => { - const result = await folderService.get("2"); + const result = await folderService.get("2", mockUserId); expect(result).toBe(undefined); }); }); it("upsert", async () => { - await folderService.upsert(folderData("2", "test 2")); + await folderService.upsert(folderData("2"), mockUserId); - expect(await firstValueFrom(folderService.folders$)).toEqual([ + expect(await firstValueFrom(folderService.folders$(mockUserId))).toEqual([ { id: "1", - name: { - encryptedString: "test", - encryptionType: 0, - }, + name: makeEncString("ENC_STRING_" + 1), revisionDate: null, }, { id: "2", - name: { - encryptedString: "test 2", - encryptionType: 0, - }, + name: makeEncString("ENC_STRING_" + 2), revisionDate: null, }, ]); }); it("replace", async () => { - await folderService.replace({ "2": folderData("2", "test 2") }, mockUserId); + await folderService.replace({ "4": folderData("4") }, mockUserId); - expect(await firstValueFrom(folderService.folders$)).toEqual([ + expect(await firstValueFrom(folderService.folders$(mockUserId))).toEqual([ { - id: "2", - name: { - encryptedString: "test 2", - encryptionType: 0, - }, + id: "4", + name: makeEncString("ENC_STRING_" + 4), revisionDate: null, }, ]); }); it("delete", async () => { - await folderService.delete("1"); + await folderService.delete("1", mockUserId); - expect((await firstValueFrom(folderService.folders$)).length).toBe(0); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(0); }); - it("clearCache", async () => { - await folderService.clearCache(); - - expect((await firstValueFrom(folderService.folders$)).length).toBe(1); - expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0); - }); - - describe("clear", () => { + describe("clearDecryptedFolderState", () => { it("null userId", async () => { - await folderService.clear(); - - expect((await firstValueFrom(folderService.folders$)).length).toBe(0); - expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0); + await expect(folderService.clearDecryptedFolderState(null)).rejects.toThrow( + "User ID is required.", + ); }); - /** - * TODO: Fix this test to address the problem where the fakes for the active user state is not - * updated as expected - */ - // it("matching userId", async () => { - // stateService.getUserId.mockResolvedValue("1"); - // await folderService.clear("1" as UserId); + it("userId provided", async () => { + await folderService.clearDecryptedFolderState(mockUserId); - // expect((await firstValueFrom(folderService.folders$)).length).toBe(0); - // }); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(1); + expect( + (await firstValueFrom(stateProvider.getUserState$(FOLDER_DECRYPTED_FOLDERS, mockUserId))) + .length, + ).toBe(0); + }); + }); - /** - * TODO: Fix this test to address the problem where the fakes for the active user state is not - * updated as expected - */ - // it("mismatching userId", async () => { - // await folderService.clear("12" as UserId); + it("clear", async () => { + await folderService.clear(mockUserId); - // expect((await firstValueFrom(folderService.folders$)).length).toBe(1); - // expect((await firstValueFrom(folderService.folderViews$)).length).toBe(2); - // }); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(0); + + const folderViews = await firstValueFrom(folderService.folderViews$(mockUserId)); + expect(folderViews.length).toBe(1); + expect(folderViews[0].id).toBeNull(); // Should be the "No Folder" folder }); describe("getRotatedData", () => { @@ -207,10 +226,10 @@ describe("Folder Service", () => { }); }); - function folderData(id: string, name: string) { + function folderData(id: string) { const data = new FolderData({} as any); data.id = id; - data.name = name; + data.name = makeEncString("ENC_STRING_" + data.id).encryptedString; return data; } diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 07b162b4f14..3aac5374fcb 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -1,14 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Observable, firstValueFrom, map, shareReplay } from "rxjs"; +import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge } from "rxjs"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { ActiveUserState, DerivedState, StateProvider } from "../../../platform/state"; +import { StateProvider } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { CipherService } from "../../../vault/abstractions/cipher.service"; @@ -21,11 +21,18 @@ import { FolderWithIdRequest } from "../../models/request/folder-with-id.request import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state"; export class FolderService implements InternalFolderServiceAbstraction { - folders$: Observable; - folderViews$: Observable; + /** + * Ensures we reuse the same observable stream for each userId rather than + * creating a new one on each folderViews$ call. + */ + private folderViewCache = new Map>(); - private encryptedFoldersState: ActiveUserState>; - private decryptedFoldersState: DerivedState; + /** + * Used to force the folderviews$ Observable to re-emit with a provided value. + * Required because shareReplay with refCount: false maintains last emission. + * Used during cleanup to force emit empty arrays, ensuring stale data isn't retained. + */ + private forceFolderViews: Record> = {}; constructor( private keyService: KeyService, @@ -33,23 +40,44 @@ export class FolderService implements InternalFolderServiceAbstraction { private i18nService: I18nService, private cipherService: CipherService, private stateProvider: StateProvider, - ) { - this.encryptedFoldersState = this.stateProvider.getActive(FOLDER_ENCRYPTED_FOLDERS); - this.decryptedFoldersState = this.stateProvider.getDerived( - this.encryptedFoldersState.state$, - FOLDER_DECRYPTED_FOLDERS, - { folderService: this, keyService: this.keyService }, - ); + ) {} - this.folders$ = this.encryptedFoldersState.state$.pipe( - map((folderData) => Object.values(folderData).map((f) => new Folder(f))), - ); + folders$(userId: UserId): Observable { + return this.encryptedFoldersState(userId).state$.pipe( + map((folders) => { + if (folders == null) { + return []; + } - this.folderViews$ = this.decryptedFoldersState.state$; + return Object.values(folders).map((f) => new Folder(f)); + }), + ); } - async clearCache(): Promise { - await this.decryptedFoldersState.forceValue([]); + /** + * Returns an Observable of decrypted folder views for the given userId. + * Uses folderViewCache to maintain a single Observable instance per user, + * combining normal folder state updates with forced updates. + */ + folderViews$(userId: UserId): Observable { + if (!this.folderViewCache.has(userId)) { + if (!this.forceFolderViews[userId]) { + this.forceFolderViews[userId] = new Subject(); + } + + const observable = merge( + this.forceFolderViews[userId], + this.encryptedFoldersState(userId).state$.pipe( + switchMap((folderData) => { + return this.decryptFolders(userId, folderData); + }), + ), + ).pipe(shareReplay({ refCount: false, bufferSize: 1 })); + + this.folderViewCache.set(userId, observable); + } + + return this.folderViewCache.get(userId); } // TODO: This should be moved to EncryptService or something @@ -60,29 +88,29 @@ export class FolderService implements InternalFolderServiceAbstraction { return folder; } - async get(id: string): Promise { - const folders = await firstValueFrom(this.folders$); + async get(id: string, userId: UserId): Promise { + const folders = await firstValueFrom(this.folders$(userId)); return folders.find((folder) => folder.id === id); } - getDecrypted$(id: string): Observable { - return this.folderViews$.pipe( + getDecrypted$(id: string, userId: UserId): Observable { + return this.folderViews$(userId).pipe( map((folders) => folders.find((folder) => folder.id === id)), shareReplay({ refCount: true, bufferSize: 1 }), ); } - async getAllFromState(): Promise { - return await firstValueFrom(this.folders$); + async getAllFromState(userId: UserId): Promise { + return await firstValueFrom(this.folders$(userId)); } /** * @deprecated For the CLI only * @param id id of the folder */ - async getFromState(id: string): Promise { - const folder = await this.get(id); + async getFromState(id: string, userId: UserId): Promise { + const folder = await this.get(id, userId); if (!folder) { return null; } @@ -93,12 +121,13 @@ export class FolderService implements InternalFolderServiceAbstraction { /** * @deprecated Only use in CLI! */ - async getAllDecryptedFromState(): Promise { - return await firstValueFrom(this.folderViews$); + async getAllDecryptedFromState(userId: UserId): Promise { + return await firstValueFrom(this.folderViews$(userId)); } - async upsert(folderData: FolderData | FolderData[]): Promise { - await this.encryptedFoldersState.update((folders) => { + async upsert(folderData: FolderData | FolderData[], userId: UserId): Promise { + await this.clearDecryptedFolderState(userId); + await this.encryptedFoldersState(userId).update((folders) => { if (folders == null) { folders = {}; } @@ -120,24 +149,31 @@ export class FolderService implements InternalFolderServiceAbstraction { if (!folders) { return; } - + await this.clearDecryptedFolderState(userId); await this.stateProvider.getUser(userId, FOLDER_ENCRYPTED_FOLDERS).update(() => { const newFolders: Record = { ...folders }; return newFolders; }); } - async clear(userId?: UserId): Promise { + async clearDecryptedFolderState(userId: UserId): Promise { if (userId == null) { - await this.encryptedFoldersState.update(() => ({})); - await this.decryptedFoldersState.forceValue([]); - } else { - await this.stateProvider.getUser(userId, FOLDER_ENCRYPTED_FOLDERS).update(() => ({})); + throw new Error("User ID is required."); } + + await this.setDecryptedFolders([], userId); } - async delete(id: string | string[]): Promise { - await this.encryptedFoldersState.update((folders) => { + async clear(userId: UserId): Promise { + this.forceFolderViews[userId]?.next([]); + + await this.encryptedFoldersState(userId).update(() => ({})); + await this.clearDecryptedFolderState(userId); + } + + async delete(id: string | string[], userId: UserId): Promise { + await this.clearDecryptedFolderState(userId); + await this.encryptedFoldersState(userId).update((folders) => { if (folders == null) { return; } @@ -164,25 +200,11 @@ export class FolderService implements InternalFolderServiceAbstraction { } } if (updates.length > 0) { - // 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.cipherService.upsert(updates.map((c) => c.toCipherData())); + await this.cipherService.upsert(updates.map((c) => c.toCipherData())); } } } - async decryptFolders(folders: Folder[]) { - const decryptFolderPromises = folders.map((f) => f.decrypt()); - const decryptedFolders = await Promise.all(decryptFolderPromises); - - decryptedFolders.sort(Utils.getSortFunction(this.i18nService, "name")); - - const noneFolder = new FolderView(); - noneFolder.name = this.i18nService.t("noneFolder"); - decryptedFolders.push(noneFolder); - return decryptedFolders; - } - async getRotatedData( originalUserKey: UserKey, newUserKey: UserKey, @@ -193,7 +215,7 @@ export class FolderService implements InternalFolderServiceAbstraction { } let encryptedFolders: FolderWithIdRequest[] = []; - const folders = await firstValueFrom(this.folderViews$); + const folders = await firstValueFrom(this.folderViews$(userId)); if (!folders) { return encryptedFolders; } @@ -205,4 +227,63 @@ export class FolderService implements InternalFolderServiceAbstraction { ); return encryptedFolders; } + + /** + * Decrypts the folders for a user. + * @param userId the user id + * @param folderData encrypted folders + * @returns a list of decrypted folders + */ + private async decryptFolders( + userId: UserId, + folderData: Record, + ): Promise { + // Check if the decrypted folders are already cached + const decrypted = await firstValueFrom( + this.stateProvider.getUser(userId, FOLDER_DECRYPTED_FOLDERS).state$, + ); + if (decrypted?.length) { + return decrypted; + } + + if (folderData == null) { + return []; + } + + const folders = Object.values(folderData).map((f) => new Folder(f)); + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + if (!userKey) { + return []; + } + + const decryptFolderPromises = folders.map((f) => + f.decryptWithKey(userKey, this.encryptService), + ); + const decryptedFolders = await Promise.all(decryptFolderPromises); + decryptedFolders.sort(Utils.getSortFunction(this.i18nService, "name")); + + const noneFolder = new FolderView(); + noneFolder.name = this.i18nService.t("noneFolder"); + decryptedFolders.push(noneFolder); + + // Cache the decrypted folders + await this.setDecryptedFolders(decryptedFolders, userId); + return decryptedFolders; + } + + /** + * @returns a SingleUserState for the encrypted folders. + */ + private encryptedFoldersState(userId: UserId) { + return this.stateProvider.getUser(userId, FOLDER_ENCRYPTED_FOLDERS); + } + + /** + * Sets the decrypted folders state for a user. + * @param folders the decrypted folders + * @param userId the user id + */ + private async setDecryptedFolders(folders: FolderView[], userId: UserId): Promise { + await this.stateProvider.setUserState(FOLDER_DECRYPTED_FOLDERS, folders, userId); + } } diff --git a/libs/common/src/vault/services/key-state/folder.state.spec.ts b/libs/common/src/vault/services/key-state/folder.state.spec.ts index ece66b5d451..217a200ea88 100644 --- a/libs/common/src/vault/services/key-state/folder.state.spec.ts +++ b/libs/common/src/vault/services/key-state/folder.state.spec.ts @@ -1,11 +1,3 @@ -import { mock } from "jest-mock-extended"; - -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { FolderService } from "../../abstractions/folder/folder.service.abstraction"; -import { FolderData } from "../../models/data/folder.data"; -import { Folder } from "../../models/domain/folder"; -import { FolderView } from "../../models/view/folder.view"; - import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "./folder.state"; describe("encrypted folders", () => { @@ -31,48 +23,32 @@ describe("encrypted folders", () => { }); describe("derived decrypted folders", () => { - const keyService = mock(); - const folderService = mock(); const sut = FOLDER_DECRYPTED_FOLDERS; - let data: FolderData; - beforeEach(() => { - data = { - id: "id", - name: "encName", - revisionDate: "2024-01-31T12:00:00.000Z", - }; + it("should deserialize decrypted folders", async () => { + const inputObj = [ + { + id: "id", + name: "encName", + revisionDate: "2024-01-31T12:00:00.000Z", + }, + ]; + + const expectedFolderView = [ + { + id: "id", + name: "encName", + revisionDate: new Date("2024-01-31T12:00:00.000Z"), + }, + ]; + + const result = sut.deserializer(JSON.parse(JSON.stringify(inputObj))); + + expect(result).toEqual(expectedFolderView); }); - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should deserialize encrypted folders", async () => { - const inputObj = [data]; - - const expectedFolderView = { - id: "id", - name: "encName", - revisionDate: new Date("2024-01-31T12:00:00.000Z"), - }; - - const result = sut.deserialize(JSON.parse(JSON.stringify(inputObj))); - - expect(result).toEqual([expectedFolderView]); - }); - - it("should derive encrypted folders", async () => { - const folderViewMock = new FolderView(new Folder(data)); - keyService.hasUserKey.mockResolvedValue(true); - folderService.decryptFolders.mockResolvedValue([folderViewMock]); - - const encryptedFoldersState = { id: data }; - const derivedStateResult = await sut.derive(encryptedFoldersState, { - folderService, - keyService, - }); - - expect(derivedStateResult).toEqual([folderViewMock]); + it("should handle null input", async () => { + const result = sut.deserializer(null); + expect(result).toEqual([]); }); }); diff --git a/libs/common/src/vault/services/key-state/folder.state.ts b/libs/common/src/vault/services/key-state/folder.state.ts index 7262d72d58e..99ad8e5ae35 100644 --- a/libs/common/src/vault/services/key-state/folder.state.ts +++ b/libs/common/src/vault/services/key-state/folder.state.ts @@ -1,34 +1,23 @@ import { Jsonify } from "type-fest"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; -import { DeriveDefinition, FOLDER_DISK, UserKeyDefinition } from "../../../platform/state"; -import { FolderService } from "../../abstractions/folder/folder.service.abstraction"; +import { FOLDER_DISK, FOLDER_MEMORY, UserKeyDefinition } from "../../../platform/state"; import { FolderData } from "../../models/data/folder.data"; -import { Folder } from "../../models/domain/folder"; import { FolderView } from "../../models/view/folder.view"; export const FOLDER_ENCRYPTED_FOLDERS = UserKeyDefinition.record( FOLDER_DISK, - "folders", + "folder", { deserializer: (obj: Jsonify) => FolderData.fromJSON(obj), clearOn: ["logout"], }, ); -export const FOLDER_DECRYPTED_FOLDERS = DeriveDefinition.from< - Record, - FolderView[], - { folderService: FolderService; keyService: KeyService } ->(FOLDER_ENCRYPTED_FOLDERS, { - deserializer: (obj) => obj.map((f) => FolderView.fromJSON(f)), - derive: async (from, { folderService, keyService }) => { - const folders = Object.values(from || {}).map((f) => new Folder(f)); - - if (await keyService.hasUserKey()) { - return await folderService.decryptFolders(folders); - } else { - return []; - } +export const FOLDER_DECRYPTED_FOLDERS = new UserKeyDefinition( + FOLDER_MEMORY, + "decryptedFolders", + { + deserializer: (obj: Jsonify) => obj?.map((f) => FolderView.fromJSON(f)) ?? [], + clearOn: ["logout", "lock"], }, -}); +); diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 0035fbdf10d..f2bf7471d44 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -16,7 +16,7 @@ import { import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import * as JSZip from "jszip"; import { concat, Observable, Subject, lastValueFrom, combineLatest, firstValueFrom } from "rxjs"; -import { filter, map, takeUntil } from "rxjs/operators"; +import { filter, map, switchMap, takeUntil } from "rxjs/operators"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -153,6 +153,8 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { private _importBlockedByPolicy = false; protected isFromAC = false; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + formGroup = this.formBuilder.group({ vaultSelector: [ "myVault", @@ -206,6 +208,7 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { @Optional() protected importCollectionService: ImportCollectionServiceAbstraction, protected toastService: ToastService, + protected accountService: AccountService, ) {} protected get importBlockedByPolicy(): boolean { @@ -257,7 +260,10 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { private handleImportInit() { // Filter out the no folder-item from folderViews$ - this.folders$ = this.folderService.folderViews$.pipe( + this.folders$ = this.activeUserId$.pipe( + switchMap((userId) => { + return this.folderService.folderViews$(userId); + }), map((folders) => folders.filter((f) => f.id != null)), ); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index 9d58bcbf559..069df8606bf 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -178,8 +178,8 @@ describe("VaultExportService", () => { const activeAccount = { id: userId, ...accountInfo }; accountService.activeAccount$ = new BehaviorSubject(activeAccount); - folderService.getAllDecryptedFromState.mockResolvedValue(UserFolderViews); - folderService.getAllFromState.mockResolvedValue(UserFolders); + folderService.folderViews$.mockReturnValue(of(UserFolderViews)); + folderService.folders$.mockReturnValue(of(UserFolders)); kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG); encryptService.encrypt.mockResolvedValue(new EncString("encrypted")); @@ -295,7 +295,7 @@ describe("VaultExportService", () => { it("exported unencrypted object contains folders", async () => { cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1)); - await folderService.getAllDecryptedFromState(); + folderService.folderViews$.mockReturnValue(of(UserFolderViews)); const actual = await exportService.getExport("json"); expectEqualFolderViews(UserFolderViews, actual); @@ -303,7 +303,7 @@ describe("VaultExportService", () => { it("exported encrypted json contains folders", async () => { cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1)); - await folderService.getAllFromState(); + folderService.folders$.mockReturnValue(of(UserFolders)); const actual = await exportService.getExport("encrypted_json"); expectEqualFolders(UserFolders, actual); 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 dbd2024d3ad..f9df9c7057f 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 @@ -32,6 +32,8 @@ export class IndividualVaultExportService extends BaseVaultExportService implements IndividualVaultExportServiceAbstraction { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private folderService: FolderService, private cipherService: CipherService, @@ -61,9 +63,10 @@ export class IndividualVaultExportService let decFolders: FolderView[] = []; let decCiphers: CipherView[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.activeUserId$); promises.push( - this.folderService.getAllDecryptedFromState().then((folders) => { + firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => { decFolders = folders; }), ); @@ -87,9 +90,10 @@ export class IndividualVaultExportService let folders: Folder[] = []; let ciphers: Cipher[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.activeUserId$); promises.push( - this.folderService.getAllFromState().then((f) => { + firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => { folders = f; }), ); @@ -102,10 +106,9 @@ export class IndividualVaultExportService await Promise.all(promises); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), + const userKey = await this.keyService.getUserKeyWithLegacySupport( + await firstValueFrom(this.activeUserId$), ); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey); const jsonDoc: BitwardenEncryptedIndividualJsonExport = { diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts index 6bf05796070..7e04972f8ab 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/vault-export.service.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -168,8 +168,8 @@ describe("VaultExportService", () => { kdfConfigService = mock(); - folderService.getAllDecryptedFromState.mockResolvedValue(UserFolderViews); - folderService.getAllFromState.mockResolvedValue(UserFolders); + folderService.folderViews$.mockReturnValue(of(UserFolderViews)); + folderService.folders$.mockReturnValue(of(UserFolders)); kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG); encryptService.encrypt.mockResolvedValue(new EncString("encrypted")); keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); @@ -294,7 +294,7 @@ describe("VaultExportService", () => { it("exported unencrypted object contains folders", async () => { cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1)); - await folderService.getAllDecryptedFromState(); + const actual = await exportService.getExport("json"); expectEqualFolderViews(UserFolderViews, actual); @@ -302,7 +302,7 @@ describe("VaultExportService", () => { it("exported encrypted json contains folders", async () => { cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1)); - await folderService.getAllFromState(); + const actual = await exportService.getExport("encrypted_json"); expectEqualFolders(UserFolders, actual); diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts index c2fbd024aa8..93a53345d3a 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts @@ -7,6 +7,7 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherId } 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"; @@ -31,12 +32,17 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { private cipherService: CipherService = inject(CipherService); private folderService: FolderService = inject(FolderService); private collectionService: CollectionService = inject(CollectionService); + private accountService = inject(AccountService); + + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); async buildConfig( mode: CipherFormMode, cipherId?: CipherId, cipherType?: CipherType, ): Promise { + const activeUserId = await firstValueFrom(this.activeUserId$); + const [organizations, collections, allowPersonalOwnership, folders, cipher] = await firstValueFrom( combineLatest([ @@ -49,9 +55,9 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { ), ), this.allowPersonalOwnership$, - this.folderService.folders$.pipe( + this.folderService.folders$(activeUserId).pipe( switchMap((f) => - this.folderService.folderViews$.pipe( + this.folderService.folderViews$(activeUserId).pipe( filter((d) => d.length - 1 === f.length), // -1 for "No Folder" in folderViews$ ), ), diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 57af96258fa..4bd87a7869d 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -1,11 +1,12 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; -import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; +import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { isCardExpired } from "@bitwarden/common/autofill/utils"; import { CollectionId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -48,6 +49,8 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide export class CipherViewComponent implements OnChanges, OnDestroy { @Input({ required: true }) cipher: CipherView | null = null; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + /** * Optional list of collections the cipher is assigned to. If none are provided, they will be fetched using the * `CipherService` and the `collectionIds` property of the cipher. @@ -66,6 +69,7 @@ export class CipherViewComponent implements OnChanges, OnDestroy { private organizationService: OrganizationService, private collectionService: CollectionService, private folderService: FolderService, + private accountService: AccountService, ) {} async ngOnChanges() { @@ -136,8 +140,14 @@ export class CipherViewComponent implements OnChanges, OnDestroy { } if (this.cipher.folderId) { + const activeUserId = await firstValueFrom(this.activeUserId$); + + if (!activeUserId) { + return; + } + this.folder$ = this.folderService - .getDecrypted$(this.cipher.folderId) + .getDecrypted$(this.cipher.folderId, activeUserId) .pipe(takeUntil(this.destroyed$)); } } From 9807b33181172c59ac5d52a3189f3d82c40147c3 Mon Sep 17 00:00:00 2001 From: Evan Bassler Date: Thu, 2 Jan 2025 16:17:47 -0600 Subject: [PATCH 044/270] add field type to show correct new cipher popup form (#12433) Co-authored-by: Evan Bassler --- apps/browser/src/autofill/background/overlay.background.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index fd16bfcf16a..8b577ccccf5 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -2275,6 +2275,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { card, identity, sender, + addNewCipherType, }: CurrentAddNewItemData) { const cipherView: CipherView = this.buildNewVaultItemCipherView({ login, @@ -2294,7 +2295,10 @@ export class OverlayBackground implements OverlayBackgroundInterface { collectionIds: cipherView.collectionIds, }); - await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id }); + await this.openAddEditVaultItemPopout(sender.tab, { + cipherId: cipherView.id, + cipherType: addNewCipherType, + }); await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher"); } catch (error) { this.logService.error("Error building cipher and opening add/edit vault item popout", error); From b370787239415e50b50b000b933d408f0493c4b7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:24:57 -0500 Subject: [PATCH 045/270] [deps] BRE: Update gh minor (#11941) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-browser.yml | 6 +- .github/workflows/build-cli.yml | 16 ++-- .github/workflows/build-desktop.yml | 82 ++++++++++----------- .github/workflows/build-web.yml | 8 +- .github/workflows/chromatic.yml | 4 +- .github/workflows/crowdin-pull.yml | 2 +- .github/workflows/release-desktop-beta.yml | 60 +++++++-------- .github/workflows/repository-management.yml | 6 +- .github/workflows/scan.yml | 4 +- .github/workflows/version-auto-bump.yml | 2 +- 10 files changed, 95 insertions(+), 95 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 68d1f10a51a..aa62d602ad8 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -169,7 +169,7 @@ jobs: zip -r browser-source.zip browser-source - name: Upload browser source - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: browser-source-${{ env._BUILD_NUMBER }}.zip path: browser-source.zip @@ -272,7 +272,7 @@ jobs: working-directory: browser-source/apps/browser - name: Upload extension artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ matrix.artifact_name }}-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/${{ matrix.archive_name }} @@ -409,7 +409,7 @@ jobs: ls -la - name: Upload Safari artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: dist-safari-${{ env._BUILD_NUMBER }}.zip path: apps/browser/dist/dist-safari.zip diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index d480879fb15..02432e0a5f4 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -163,14 +163,14 @@ jobs: matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt - name: Upload unix zip asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload unix checksum asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -324,14 +324,14 @@ jobs: -t sha256 | Out-File -Encoding ASCII ./dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${env:_PACKAGE_VERSION}.txt - name: Upload windows zip asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload windows checksum asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -339,7 +339,7 @@ jobs: - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg @@ -350,7 +350,7 @@ jobs: - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip path: apps/cli/bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip @@ -421,14 +421,14 @@ jobs: run: sudo snap remove bw - name: Upload snap asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload snap checksum asset - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 2a59dc28fc9..63453b93838 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -207,7 +207,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -232,42 +232,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -280,7 +280,7 @@ jobs: sudo npm run pack:lin:flatpak - name: Upload flatpak artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: com.bitwarden.desktop.flatpak path: apps/desktop/dist/com.bitwarden.desktop.flatpak @@ -373,7 +373,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -428,91 +428,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -561,14 +561,14 @@ jobs: - name: Cache Build id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Cache Safari id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -681,7 +681,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -749,14 +749,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -869,7 +869,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -918,28 +918,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -990,14 +990,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -1117,7 +1117,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -1166,7 +1166,7 @@ jobs: run: npm run pack:mac:mas - name: Upload .pkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -1193,7 +1193,7 @@ jobs: if: | github.event_name != 'pull_request_target' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop') - uses: slackapi/slack-github-action@37ebaef184d7626c5f204ab8d3baff4262dd30f0 # v1.27.0 + uses: slackapi/slack-github-action@fcfb566f8b0aab22203f066d80ca1d7e4b5d05b3 # v1.27.1 with: channel-id: C074F5UESQ0 payload: | @@ -1252,14 +1252,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -1372,7 +1372,7 @@ jobs: npm link ../sdk-internal - name: Cache Native Module - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: cache with: path: | @@ -1424,7 +1424,7 @@ jobs: zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - name: Upload masdev artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index c686b46d51a..73ae0e14962 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -164,7 +164,7 @@ jobs: run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build - name: Upload ${{ matrix.name }} artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: web-${{ env._VERSION }}-${{ matrix.name }}.zip path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip @@ -274,7 +274,7 @@ jobs: - name: Build Docker image id: build-docker - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: context: apps/web file: apps/web/Dockerfile @@ -303,14 +303,14 @@ jobs: - name: Scan Docker image id: container-scan - uses: anchore/scan-action@5ed195cc06065322983cae4bb31e2a751feb86fd # v5.2.0 + uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0 with: image: ${{ steps.image-name.outputs.name }} fail-build: false output-format: sarif - name: Upload Grype results to GitHub - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 0efd9d22f17..a5ebd363f63 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -43,7 +43,7 @@ jobs: - name: Cache NPM id: npm-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: "~/.npm" key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }} @@ -56,7 +56,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@dd2eecb9bef44f54774581f4163b0327fd8cf607 # v11.16.3 + uses: chromaui/action@64a9c0ca3bfb724389b0d536e544f56b7b5ff5b3 # v11.20.2 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 1ac6dd9113f..027a2f11e55 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -22,7 +22,7 @@ jobs: crowdin_project_id: "308189" steps: - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index a940ce289ff..3ec11c77852 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -158,42 +158,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release-channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-linux.yml @@ -299,91 +299,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release-channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release-channel }}.yml @@ -426,14 +426,14 @@ jobs: - name: Cache Build id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Cache Safari id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -560,14 +560,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -707,28 +707,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ needs.setup.outputs.release-channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-mac.yml @@ -773,14 +773,14 @@ jobs: - name: Get Build Cache id: build-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/desktop/build key: ${{ runner.os }}-${{ github.run_id }}-build - name: Setup Safari Cache id: safari-cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: apps/browser/dist/Safari key: ${{ runner.os }}-${{ github.run_id }}-safari-extension @@ -915,7 +915,7 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - name: Upload .pkg artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index aeafe17e5e0..a914a2c4a7a 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -115,7 +115,7 @@ jobs: version: ${{ inputs.version_number_override }} - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -452,7 +452,7 @@ jobs: - setup steps: - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 6166ac79b1a..b0874b38cbf 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -31,7 +31,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36 + uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: @@ -46,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: sarif_file: cx_result.sarif diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index f41261cb39a..ef46dbc867d 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} From aeba2b3c73e953111c0907eba68d06596a5f6a12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 22:29:53 -0500 Subject: [PATCH 046/270] [deps] Platform: Update webpack-cli to v6 (#12552) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 130 +++++++++++++++++++++++++++------------------- package.json | 2 +- 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index 197b7cb0093..ba7953d0faf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -179,7 +179,7 @@ "util": "0.12.5", "wait-on": "8.0.1", "webpack": "5.97.1", - "webpack-cli": "5.1.4", + "webpack-cli": "6.0.1", "webpack-dev-server": "5.2.0", "webpack-node-externals": "3.0.0" }, @@ -10338,45 +10338,45 @@ "license": "BSD-3-Clause" }, "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -18831,6 +18831,16 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/into-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", @@ -27431,6 +27441,19 @@ "node": ">=0.10.0" } }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -31919,43 +31942,40 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", "colorette": "^2.0.14", - "commander": "^10.0.1", + "commander": "^12.1.0", "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", + "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "webpack": "^5.82.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -31964,37 +31984,39 @@ } } }, + "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/webpack-cli/node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-cli/node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "node_modules/webpack-cli/node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "license": "MIT", "dependencies": { - "resolve": "^1.20.0" + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">=18.0.0" } }, "node_modules/webpack-dev-middleware": { diff --git a/package.json b/package.json index 907723c3a02..843e9f34bb6 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "util": "0.12.5", "wait-on": "8.0.1", "webpack": "5.97.1", - "webpack-cli": "5.1.4", + "webpack-cli": "6.0.1", "webpack-dev-server": "5.2.0", "webpack-node-externals": "3.0.0" }, From b0f597128720331d7d0e19dcf08256be539f4d09 Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Fri, 3 Jan 2025 10:30:25 +0100 Subject: [PATCH 047/270] [PM-16664] Fix annual pricing for billable providers (#12662) --- .../provider-subscription.component.html | 13 +++++++------ .../provider-subscription.component.ts | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html index 50db2cb6a36..d23216cf7c1 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html @@ -38,12 +38,14 @@ }} - {{ ((100 - subscription.discountPercentage) / 100) * i.cost | currency: "$" }} /{{ - "month" | i18n + {{ + ((100 - subscription.discountPercentage) / 100) * getMonthlyCost(i) + | currency: "$" }} + / {{ "month" | i18n }}
    - {{ i.cost | currency: "$" }} /{{ "month" | i18n }} + {{ getMonthlyCost(i) | currency: "$" }} / {{ "month" | i18n }}
    @@ -52,9 +54,8 @@ - Total: {{ totalCost | currency: "$" }} /{{ - "month" | i18n - }} + Total: + {{ getTotalMonthlyCost() | currency: "$" }} / {{ "month" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index a6b27b9f3dd..8d222ec42bd 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -113,4 +113,20 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { } }); } + + protected getMonthlyCost(plan: ProviderPlanResponse): number { + return plan.cadence === "Monthly" ? plan.cost : plan.cost / 12; + } + + protected getDiscountedMonthlyCost(plan: ProviderPlanResponse): number { + return ((100 - this.subscription.discountPercentage) / 100) * this.getMonthlyCost(plan); + } + + protected getTotalMonthlyCost(): number { + let totalCost: number = 0; + for (const plan of this.subscription.plans) { + totalCost += this.getDiscountedMonthlyCost(plan); + } + return totalCost; + } } From 27e3f72e048c228b29b0770aed1fd6a2ab59e18d Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Fri, 3 Jan 2025 10:46:35 +0100 Subject: [PATCH 048/270] Revert "[PM-16664] Fix annual pricing for billable providers (#12662)" (#12677) This reverts commit b0f597128720331d7d0e19dcf08256be539f4d09. --- .../provider-subscription.component.html | 13 ++++++------- .../provider-subscription.component.ts | 16 ---------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html index d23216cf7c1..50db2cb6a36 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html @@ -38,14 +38,12 @@ }} - {{ - ((100 - subscription.discountPercentage) / 100) * getMonthlyCost(i) - | currency: "$" + {{ ((100 - subscription.discountPercentage) / 100) * i.cost | currency: "$" }} /{{ + "month" | i18n }} - / {{ "month" | i18n }}
    - {{ getMonthlyCost(i) | currency: "$" }} / {{ "month" | i18n }} + {{ i.cost | currency: "$" }} /{{ "month" | i18n }}
    @@ -54,8 +52,9 @@ - Total: - {{ getTotalMonthlyCost() | currency: "$" }} / {{ "month" | i18n }} + Total: {{ totalCost | currency: "$" }} /{{ + "month" | i18n + }} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index 8d222ec42bd..a6b27b9f3dd 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -113,20 +113,4 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { } }); } - - protected getMonthlyCost(plan: ProviderPlanResponse): number { - return plan.cadence === "Monthly" ? plan.cost : plan.cost / 12; - } - - protected getDiscountedMonthlyCost(plan: ProviderPlanResponse): number { - return ((100 - this.subscription.discountPercentage) / 100) * this.getMonthlyCost(plan); - } - - protected getTotalMonthlyCost(): number { - let totalCost: number = 0; - for (const plan of this.subscription.plans) { - totalCost += this.getDiscountedMonthlyCost(plan); - } - return totalCost; - } } From 1f00eb1bfb7703db9e9686c62084274f57617c60 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:05:20 +0100 Subject: [PATCH 049/270] Autosync the updated translations (#12671) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/hr/messages.json | 2 +- apps/browser/src/_locales/nl/messages.json | 186 +++++++++--------- apps/browser/src/_locales/pl/messages.json | 2 +- apps/browser/src/_locales/pt_PT/messages.json | 2 +- apps/browser/src/_locales/sr/messages.json | 34 ++-- apps/browser/src/_locales/uk/messages.json | 2 +- apps/browser/src/_locales/zh_CN/messages.json | 18 +- apps/browser/store/locales/nl/copy.resx | 58 +++--- 8 files changed, 152 insertions(+), 152 deletions(-) diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 3b60cfe45c8..c93922cb913 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -1005,7 +1005,7 @@ "message": "Prikazuj identitete za jednostavnu auto-ispunu." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Klikni stavke za auto-ispunu na prikazu trezora" }, "clearClipboard": { "message": "Očisti međuspremnik", diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 7a252ee3ddb..d167ba220c1 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -181,7 +181,7 @@ "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { - "message": "Auto-invullen" + "message": "Automatisch invullen" }, "autoFillLogin": { "message": "Login automatisch invullen" @@ -688,7 +688,7 @@ "message": "Nu vergrendelen" }, "lockAll": { - "message": "Vergrendel alles" + "message": "Alles vergrendelen" }, "immediately": { "message": "Onmiddellijk" @@ -1043,7 +1043,7 @@ "message": "Ja, nu bijwerken" }, "notificationUnlockDesc": { - "message": "Ontgrendel je Bitwarden-kluis om het auto-invulverzoek te voltooien." + "message": "Ontgrendel je Bitwarden-kluis om het automatisch invullen verzoek te voltooien." }, "notificationUnlock": { "message": "Ontgrendelen" @@ -1200,7 +1200,7 @@ "message": "Geen bijlagen." }, "attachmentSaved": { - "message": "De bijlage is opgeslagen." + "message": "Bijlage opgeslagen" }, "file": { "message": "Bestand" @@ -1209,7 +1209,7 @@ "message": "Bestand om te delen" }, "selectFile": { - "message": "Selecteer een bestand." + "message": "Selecteer een bestand" }, "maxFileSize": { "message": "Maximale bestandsgrootte is 500 MB." @@ -1242,7 +1242,7 @@ "message": "1 GB versleutelde opslag voor bijlagen." }, "premiumSignUpEmergency": { - "message": "Noodtoegang" + "message": "Noodtoegang." }, "premiumSignUpTwoStepOptions": { "message": "Eigen opties voor tweestapsaanmelding zoals YubiKey en Duo." @@ -1425,10 +1425,10 @@ "message": "Specificeer de basis-URL van je zelfgehoste Bitwarden-installatie. Bijvoorbeeld: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Voor geavanceerde configuratie kun je de basis URL van elke service onafhankelijk opgeven." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Je moet de basis URL van de server of ten minste één aangepaste omgeving toevoegen." }, "customEnvironment": { "message": "Aangepaste omgeving" @@ -1459,14 +1459,14 @@ "message": "Pictogrammenserver-URL" }, "environmentSaved": { - "message": "De omgeving-URL's zijn opgeslagen." + "message": "Omgeving-URL's opgeslagen" }, "showAutoFillMenuOnFormFields": { - "message": "Auto-invulmenu op formuliervelden weergeven", + "message": "Automatisch invullen menu op formuliervelden weergeven", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Suggesties voor automatisch invullen" + "message": "Suggesties automatisch invullen" }, "showInlineMenuLabel": { "message": "Suggesties voor automatisch invullen op formuliervelden weergeven" @@ -1511,7 +1511,7 @@ "message": "Als een inlogformulier wordt gedetecteerd, dan worden de inloggegevens automatisch ingevuld." }, "experimentalFeature": { - "message": "Gehackte of onbetrouwbare websites kunnen auto-invullen tijdens het laden van de pagina misbruiken." + "message": "Gehackte of onbetrouwbare websites kunnen automatisch invullen tijdens het laden van de pagina misbruiken." }, "learnMoreAboutAutofillOnPageLoadLinkText": { "message": "Meer over risico's lezen" @@ -1535,7 +1535,7 @@ "message": "Automatisch invullen bij laden van pagina" }, "autoFillOnPageLoadNo": { - "message": "Niet Automatisch invullen bij laden van pagina" + "message": "Niet automatisch invullen bij laden van pagina" }, "commandOpenPopup": { "message": "Open kluis in pop-up" @@ -1606,7 +1606,7 @@ "message": "Een herkenbare afbeelding naast iedere login weergeven." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Toon een herkenbare afbeelding naast elke login. Geldt voor alle ingelogde accounts." }, "enableBadgeCounter": { "message": "Teller weergeven" @@ -1690,7 +1690,7 @@ "message": "Dr." }, "mx": { - "message": "Mx" + "message": "Mx." }, "firstName": { "message": "Voornaam" @@ -1993,10 +1993,10 @@ "message": "Ontgrendelen met PIN" }, "setYourPinTitle": { - "message": "PIN-code instellen" + "message": "PIN instellen" }, "setYourPinButton": { - "message": "PIN-code instellen" + "message": "PIN instellen" }, "setYourPinCode": { "message": "Stel je PIN-code in voor het ontgrendelen van Bitwarden. Je PIN-code wordt opnieuw ingesteld als je je ooit volledig afmeldt bij de applicatie." @@ -2111,10 +2111,10 @@ "message": "Invullen en opslaan" }, "autoFillSuccessAndSavedUri": { - "message": "Automatisch gevuld item en opgeslagen URI" + "message": "Automatisch ingevuld item en opgeslagen URI" }, "autoFillSuccess": { - "message": "Automatisch gevuld item" + "message": "Item automatisch ingevuld " }, "insecurePageWarning": { "message": "Waarschuwing: Dit is een onbeveiligde HTTP-pagina waardoor anderen alle informatie die je verstuurt kunnen zien en wijzigen. Deze login is oorspronkelijk opgeslagen op een beveiligde (HTTPS) pagina." @@ -2225,7 +2225,7 @@ "message": "Fout bij vernieuwen toegangstoken" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Geen verversingstoken of API-sleutels gevonden. Probeer uit te loggen en weer in te loggen." }, "desktopSyncVerificationTitle": { "message": "Desktopsynchronisatieverificatie" @@ -2282,10 +2282,10 @@ "message": "Dit apparaat ondersteunt geen browserbiometrie." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Gebruiker vergrendeld of uitgelogd" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Ontgrendel deze gebruiker in de desktopapplicatie en probeer het opnieuw." }, "biometricsNotAvailableTitle": { "message": "Biometrisch ontgrendelen niet beschikbaar" @@ -2623,11 +2623,11 @@ "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "De machtigingen van je organisatie zijn bijgewerkt, waardoor je een hoofdwachtwoord moet instellen.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "Je organisatie vereist dat je een hoofdwachtwoord instelt.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -2766,7 +2766,7 @@ "message": "Persoonlijke kluis exporteren" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "Alleen de individuele kluisitems die gekoppeld zijn aan $EMAIL$ worden geëxporteerd. Kluisitems van organisaties worden niet meegenomen. Alleen de informatie over het kluisitem wordt geëxporteerd en niet de bijbehorende bijlagen.", "placeholders": { "email": { "content": "$1", @@ -2869,7 +2869,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ fout: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2887,7 +2887,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Website: $WEBSITE$. Gegenereerd door Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2897,7 +2897,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ongeldig $SERVICENAME$ API token", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2907,7 +2907,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ongeldige $SERVICENAME$ API token: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2921,7 +2921,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Kan $SERVICENAME$ gemaskeerde e-mailaccount-ID niet verkrijgen.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2931,7 +2931,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ongeldig $SERVICENAME$ domein.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2941,7 +2941,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Ongeldige $SERVICENAME$ url.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2951,7 +2951,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Onbekende $SERVICENAME$ fout opgetreden.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2961,7 +2961,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Onbekende doorstuurder: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3017,7 +3017,7 @@ "message": "zelfgehost" }, "thirdParty": { - "message": "van derden" + "message": "Derde partij" }, "thirdPartyServerMessage": { "message": "Verbonden met server implementatie van derden, $SERVERNAME$. Reproduceer bugs via de officiële server, of meld ze bij het serverproject.", @@ -3071,7 +3071,7 @@ "message": "Alle inlogopties bekijken" }, "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Alle inlogopties weergeven" }, "notificationSentDevice": { "message": "Er is een melding naar je apparaat verzonden." @@ -3125,10 +3125,10 @@ "message": "Je organisatiebeleid heeft het automatisch invullen bij laden van pagina ingeschakeld." }, "howToAutofill": { - "message": "Hoe automatisch aanvullen" + "message": "Hoe automatisch invullen" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "Selecteer een item in dit scherm, gebruik de sneltoets $COMMAND$ of verken andere opties in de instellingen.", "placeholders": { "command": { "content": "$1", @@ -3137,16 +3137,16 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "Selecteer een item in dit scherm of verken andere opties in de instellingen." }, "gotIt": { - "message": "Ik snap het" + "message": "Oké" }, "autofillSettings": { "message": "Instellingen automatisch invullen" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Shortcut voor automatisch invullen" + "message": "Snelkoppeling automatisch invullen" }, "autofillKeyboardShortcutUpdateLabel": { "message": "Snelkoppeling wijzigen" @@ -3243,7 +3243,7 @@ "message": "Algemeen" }, "display": { - "message": "Display" + "message": "Weergave" }, "accountSuccessfullyCreated": { "message": "Account succesvol aangemaakt!" @@ -3372,7 +3372,7 @@ "message": "-- Type om te filteren --" }, "multiSelectLoading": { - "message": "Opties ophalen..." + "message": "Opties ophalen…" }, "multiSelectNotFound": { "message": "Geen items gevonden" @@ -3413,7 +3413,7 @@ "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importeren...", + "message": "Importeren…", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { @@ -3432,33 +3432,33 @@ "message": "Aliasdomein" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Items with master password re-prompt cannot be autofilled on page load. Autofill on page load turned off.", + "message": "Items met hoofdwachtwoord re-prompt kunnen niet automatisch worden ingevuld bij het laden van de pagina. Automatisch invullen bij laden van pagina uitgeschakeld.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Autofill on page load set to use default setting.", + "message": "Automatisch invullen bij het laden van een pagina ingesteld op de standaardinstelling.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { - "message": "Turn off master password re-prompt to edit this field", + "message": "Hoofdwachtwoord herhaalprompt uitschakelen om dit veld te bewerken", "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { "message": "Zijnavigatie schakelen" }, "skipToContent": { - "message": "Skip to content" + "message": "Overslaan naar inhoud" }, "bitwardenOverlayButton": { "message": "Menuknop Bitwarden automatisch invullen", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Bitwarden auto-invulmenu in- en uitschakelen", + "message": "Bitwarden automatisch invullen menu in- en uitschakelen", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden auto-invulmenu", + "message": "Bitwarden automatisch invullen menu", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { @@ -3486,7 +3486,7 @@ "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { - "message": "Inloggegevens invullen voor", + "message": "Referenties invullen voor", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { @@ -3530,7 +3530,7 @@ "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden auto-invulmenu beschikbaar. Druk op de pijltjestoets omlaag om te selecteren.", + "message": "Bitwarden automatisch invullen menu beschikbaar. Druk op de pijltjestoets omlaag om te selecteren.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -3574,7 +3574,7 @@ "message": "Deze actie vereist verificatie actie. Stel een pincode in om door te gaan." }, "setPin": { - "message": "Pincode instellen" + "message": "PIN instellen" }, "verifyWithBiometrics": { "message": "Biometrisch ontgrendelen" @@ -3619,7 +3619,7 @@ "message": "Fout bij het verbinden met de Duo-service. Gebruik een andere tweestapsaanmeldingsmethode of neem contact op met Duo voor hulp." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Start Duo en volg de stappen om het inloggen te voltooien." }, "duoRequiredForAccount": { "message": "Jouw account vereist Duo-tweestapsaanmelding." @@ -3628,10 +3628,10 @@ "message": "Open de extensie om in te loggen." }, "popoutExtension": { - "message": "Popout extension" + "message": "Pop-out extensie" }, "launchDuo": { - "message": "Launch Duo" + "message": "Duo starten" }, "importFormatError": { "message": "De gegevens zijn niet correct opgemaakt. Controleer je importbestand en probeer het opnieuw." @@ -3759,7 +3759,7 @@ "message": "Kies een passkey om mee in te loggen" }, "passkeyItem": { - "message": "Passkey-Item" + "message": "Passkey item" }, "overwritePasskey": { "message": "Passkey overschrijven?" @@ -3801,7 +3801,7 @@ "message": "LastPass Email" }, "importingYourAccount": { - "message": "Account impoteren..." + "message": "Account impoteren…" }, "lastPassMFARequired": { "message": "LastPass multifactor-authenticatie vereist" @@ -3825,10 +3825,10 @@ "message": "Wacht op SSO-authenticatie" }, "awaitingSSODesc": { - "message": "Ga door met inloggen met de inloggegevens van je bedrijf." + "message": "Ga door met inloggen met de referenties van je bedrijf." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "Zie gedetailleerde instructies op onze helpsite op", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { @@ -3844,52 +3844,52 @@ "message": "Collectie" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "Steek de YubiKey die bij je LastPass account hoort in de USB poort van je computer en druk dan op de knop." }, "switchAccount": { - "message": "Switch account" + "message": "Account wisselen" }, "switchAccounts": { - "message": "Switch accounts" + "message": "Accounts wisselen" }, "switchToAccount": { - "message": "Switch to account" + "message": "Wisselen naar account" }, "activeAccount": { - "message": "Active account" + "message": "Actief account" }, "availableAccounts": { "message": "Beschikbare accounts" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "Accountlimiet bereikt. Log uit bij een account om een andere toe te voegen." }, "active": { - "message": "active" + "message": "actief" }, "locked": { - "message": "locked" + "message": "vergrendeld" }, "unlocked": { - "message": "unlocked" + "message": "ontgrendeld" }, "server": { "message": "server" }, "hostedAt": { - "message": "hosted at" + "message": "gehost op" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Gebruik je apparaat of hardwaresleutel" }, "justOnce": { - "message": "Just once" + "message": "Eenmalig" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "Altijd voor deze site" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ toegevoegd aan uitgesloten domeinen.", "placeholders": { "domain": { "content": "$1", @@ -3950,7 +3950,7 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Referenties succesvol opgeslagen!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { @@ -3958,7 +3958,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Referenties succesvol bijgewerkt!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { @@ -3966,7 +3966,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Fout bij het opslaan van referenties. Controleer console voor details.", "description": "Notification message for when saving credentials has failed." }, "success": { @@ -3979,7 +3979,7 @@ "message": "Passkey verwijderd" }, "autofillSuggestions": { - "message": "Suggesties voor automatisch invullen" + "message": "Suggesties automatisch invullen" }, "autofillSuggestionsTip": { "message": "Inlogitem opslaan voor automatisch invullen op deze site" @@ -3994,7 +3994,7 @@ "message": "Wis filters of probeer een andere zoekterm" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "Info kopiëren - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4004,7 +4004,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "Notitie kopiëren - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4014,7 +4014,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Meer opties, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4024,7 +4024,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Meer opties - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4034,7 +4034,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Item bekijken - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4060,22 +4060,22 @@ "message": "Aan collecties toewijzen" }, "copyEmail": { - "message": "Copy email" + "message": "E-mail kopiëren" }, "copyPhone": { - "message": "Copy phone" + "message": "Telefoon kopiëren" }, "copyAddress": { - "message": "Copy address" + "message": "Adres kopiëren" }, "adminConsole": { - "message": "Admin Console" + "message": "Beheerconsole" }, "accountSecurity": { "message": "Accountbeveiliging" }, "notifications": { - "message": "Notifications" + "message": "Meldingen" }, "appearance": { "message": "Uiterlijk" @@ -4107,7 +4107,7 @@ } }, "new": { - "message": "New" + "message": "Nieuw" }, "removeItem": { "message": "Verwijder $NAME$", @@ -4245,7 +4245,7 @@ "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Inloggegevens" + "message": "Login referenties" }, "authenticatorKey": { "message": "Authenticatiesleutel" @@ -4546,10 +4546,10 @@ "message": "Itemlocatie" }, "fileSend": { - "message": "Bestand-Sends" + "message": "Bestand verzenden" }, "fileSends": { - "message": "Bestand-Sends" + "message": "Bestanden verzenden" }, "textSend": { "message": "Tekst-Sends" @@ -4567,7 +4567,7 @@ "message": "Accountacties" }, "showNumberOfAutofillSuggestions": { - "message": "Aantal login-autofill-suggesties op het extensie-pictogram weergeven" + "message": "Aantal login automatisch invullen suggesties op het extensie-pictogram weergeven" }, "showQuickCopyActions": { "message": "Toon snelle kopieeracties in de kluis" diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 5d1cd22c9ef..a429059ea7d 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1005,7 +1005,7 @@ "message": "Pokaż elementy tożsamości na stronie głównej, aby ułatwić autouzupełnianie." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Kliknij na dane logowania, aby autouzupełnić w widoku Sejfu" }, "clearClipboard": { "message": "Wyczyść schowek", diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 24caa7e813d..def50289ae6 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -803,7 +803,7 @@ "message": "Código de verificação inválido" }, "valueCopied": { - "message": "$VALUE$ copiado", + "message": "$VALUE$ copiado(a)", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 1a1ebf69794..d515c2a0c6b 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -193,10 +193,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": { @@ -1005,7 +1005,7 @@ "message": "Прикажи ставке идентитета на страници за лакше аутоматско допуњавање." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Кликните на ставке за ауто-попуњавање у приказу сефа" }, "clearClipboard": { "message": "Обриши привремену меморију", @@ -3071,7 +3071,7 @@ "message": "Погледајте сав извештај у опције" }, "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Погледајте сав извештај у опције" }, "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." @@ -3478,11 +3478,11 @@ "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "Једнократни верификациони кôд заснован на времену", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "Преостало време до истека актуелног ТОТП-а", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { @@ -4570,7 +4570,7 @@ "message": "Прикажи број предлога за ауто-попуњавање пријаве на икони додатка" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "Приказати брзе радње копирања у Сефу" }, "systemDefault": { "message": "Системски подразумевано" @@ -4804,22 +4804,22 @@ "message": "Бета" }, "importantNotice": { - "message": "Important notice" + "message": "Важно обавештење" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Поставити дво-степенску пријаву" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden ће послати кôд на имејл вашег налога за верификовање пријављивања са нових уређаја почевши од фебруара 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": "Можете да подесите пријаву у два корака као алтернативни начин да заштитите свој налог или да промените свој имејл у један који можете да приступите." }, "remindMeLater": { - "message": "Remind me later" + "message": "Подсети ме касније" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Да ли имате поуздан приступ својим имејлом, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -4828,16 +4828,16 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Не, ненам" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Да, могу поуздано да приступим овим имејлом" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Упалити дво-степенску пријаву" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Променити имејл налога" }, "extensionWidth": { "message": "Ширина додатка" diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index c817b55dab3..8e20bc56ff5 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -1005,7 +1005,7 @@ "message": "Показувати список посвідчень на сторінці вкладки для легкого автозаповнення." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Натисніть на запис у режимі перегляду сховища для автозаповнення" }, "clearClipboard": { "message": "Очистити буфер обміну", diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 39a945360f8..9af42f75e08 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -284,7 +284,7 @@ "message": "前往网页 App 吗?" }, "continueToWebAppDesc": { - "message": "在网页应用上探索 Bitwarden 账户的更多功能。" + "message": "在网页 App 上探索 Bitwarden 账户的更多功能。" }, "continueToHelpCenter": { "message": "前往帮助中心吗?" @@ -299,7 +299,7 @@ "message": "帮助别人了解 Bitwarden 是否适合他们。立即访问浏览器的扩展程序商店并留下评分。" }, "changeMasterPasswordOnWebConfirmation": { - "message": "您可以在 Bitwarden 网页应用上更改您的主密码。" + "message": "您可以在 Bitwarden 网页 App 上更改您的主密码。" }, "fingerprintPhrase": { "message": "指纹短语", @@ -597,7 +597,7 @@ "message": "前往" }, "launchWebsite": { - "message": "启动网站" + "message": "打开网站" }, "launchWebsiteName": { "message": "前往 $ITEMNAME$ 的网站", @@ -763,7 +763,7 @@ "message": "必须填写确认主密码。" }, "masterPasswordMinlength": { - "message": "主密码必须至少 $VALUE$ 个字符长度。", + "message": "主密码长度必须至少为 $VALUE$ 个字符。", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -1071,7 +1071,7 @@ "message": "主题" }, "themeDesc": { - "message": "更改本应用程序的颜色主题。" + "message": "更改应用程序的颜色主题。" }, "themeDescAlt": { "message": "更改应用程序的颜色主题。适用于所有已登录的账户。" @@ -1133,7 +1133,7 @@ "message": "确认密码库导出" }, "exportWarningDesc": { - "message": "导出的密码库数据包含未加密格式。您不应该通过不安全的渠道(例如电子邮件)来存储或发送导出的文件。用完后请立即将其删除。" + "message": "此导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。使用完后请立即将其删除。" }, "encExportKeyWarningDesc": { "message": "此导出将使用您账户的加密密钥来加密您的数据。如果您曾经轮换过账户的加密密钥,您应将其重新导出,否则您将无法解密导出的文件。" @@ -4810,7 +4810,7 @@ "message": "设置两步登录" }, "newDeviceVerificationNoticeContentPage1": { - "message": "从 2025 年 02 月开始,Bitwarden 将向您的账户电子邮箱发送一个代码,以验证来自新设备的登录。" + "message": "从 2025 年 02 月起,当有来自新设备的登录时,Bitwarden 将向您的账户电子邮箱发送验证码。" }, "newDeviceVerificationNoticeContentPage2": { "message": "您可以设置两步登录作为保护账户的替代方法,或将您的电子邮箱更改为您可以访问的电子邮箱。" @@ -4819,7 +4819,7 @@ "message": "稍后提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "您能可靠地访问您的电子邮箱 $EMAIL$ 吗?", + "message": "您能可正常访问您的电子邮箱 $EMAIL$ 吗?", "placeholders": { "email": { "content": "$1", @@ -4831,7 +4831,7 @@ "message": "不,我不能" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "是的,我可以可靠地访问我的电子邮箱" + "message": "是的,我可以正常访问我的电子邮箱" }, "turnOnTwoStepLogin": { "message": "开启两步登录" diff --git a/apps/browser/store/locales/nl/copy.resx b/apps/browser/store/locales/nl/copy.resx index 6e646d5ece8..17720f54f4c 100644 --- a/apps/browser/store/locales/nl/copy.resx +++ b/apps/browser/store/locales/nl/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - wachtwoordbeheerder + Bitwarden Wachtwoordbeheerder - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Thuis, op het werk of onderweg, Bitwarden beveiligt eenvoudig al je wachtwoorden, sleutels en gevoelige informatie. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Erkend als de beste wachtwoordmanager door PCMag, WIRED, The Verge, CNET, G2 en meer! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +BEVEILIG JE DIGITALE LEVEN +Beveilig je digitale leven en bescherm je tegen datalekken door unieke, sterke wachtwoorden te genereren en op te slaan voor elke account. Bewaar alles in een end-to-end versleutelde wachtwoordkluis waar alleen jij toegang toe hebt. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +OVERAL EN ALTIJD TOEGANG TOT JE GEGEVENS, OP ELK APPARAAT +Beheer, bewaar, beveilig en deel een onbeperkt aantal wachtwoorden op een onbeperkt aantal apparaten zonder beperkingen. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +IEDEREEN ZOU DE MIDDELEN MOETEN HEBBEN OM VEILIG ONLINE TE BLIJVEN +Gebruik Bitwarden gratis, zonder advertenties of verkoop van gegevens. Bitwarden vindt dat iedereen de mogelijkheid moet hebben om veilig online te zijn. Premium abonnementen bieden toegang tot geavanceerde functies. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +VERSTERK JE TEAMS MET BITWARDEN +Abonnementen voor Teams en Enterprise worden geleverd met professionele zakelijke functies. Enkele voorbeelden zijn SSO-integratie, zelf hosten, directory-integratie en SCIM provisioning, globaal beleid, API-toegang, gebeurtenislogboeken en meer. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Gebruik Bitwarden om je medewerkers te beveiligen en gevoelige informatie te delen met collega's. -More reasons to choose Bitwarden: +Meer redenen om voor Bitwarden te kiezen: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Encryptie van wereldklasse +Wachtwoorden worden beschermd met geavanceerde end-to-end versleuteling (AES-256 bit, salted hashtag en PBKDF2 SHA-256) zodat je gegevens veilig en privé blijven. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. +Audits door derde partijen +Bitwarden voert regelmatig uitgebreide beveiligingsaudits uit bij gerenommeerde beveiligingsbedrijven. Deze jaarlijkse audits omvatten broncodebeoordelingen en penetratietests voor Bitwarden IP's, servers en webapplicaties. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Geavanceerde 2FA +Beveilig je login met een authenticator van derden, codes per e-mail of FIDO2 WebAuthn referenties zoals een hardware beveiligingssleutel of passkey. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Verstuur gegevens rechtstreeks naar anderen met behoud van end-to-end versleutelde beveiliging en beperking van blootstelling. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +Ingebouwde generator +Maak lange, complexe en duidelijke wachtwoorden en unieke gebruikersnamen voor elke site die je bezoekt. Integreer met e-mail alias providers voor extra privacy. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Wereldwijde vertalingen +Bitwarden vertalingen bestaan voor meer dan 60 talen, vertaald door de wereldwijde gemeenschap via Crowdin. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Platformoverkoepelende applicaties +Beveilig en deel gevoelige gegevens in je Bitwarden Vault vanuit elke browser, mobiel apparaat of desktop OS, en meer. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden beveiligt meer dan alleen wachtwoorden +Met de end-to-end versleutelde oplossingen voor referentiebeheer van Bitwarden kunnen organisaties alles beveiligen, inclusief ontwikkelaarsgeheimen en ervaringen met wachtwoorden. Bezoek Bitwarden.com voor meer informatie over Bitwarden Secrets Manager en Bitwarden Passwordless.dev! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Thuis, op het werk of onderweg, Bitwarden beveiligt eenvoudig al je wachtwoorden, sleutels en gevoelige informatie. Synchroniseer en gebruik je kluis op meerdere apparaten From 654eef1de27ffd45798e8a9694a6a529e373381f Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:11:12 +0100 Subject: [PATCH 050/270] Remove v1 account switcher (#12577) Remove conditional rendering for extensionRefresh feature flag Co-authored-by: Daniel James Smith --- .../account-switcher.component.html | 240 +++++------------- .../account-switcher.component.ts | 9 - .../account-switching/account.component.html | 123 +++------ .../account-switching/account.component.ts | 1 - 4 files changed, 97 insertions(+), 276 deletions(-) diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html index f0723d75ff8..0152cd1c7ff 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html @@ -1,182 +1,78 @@ - - - - - - - - - - - - -
    - -
    - - - -

    {{ "availableAccounts" | i18n }}

    -
    - -
    - -
    -
    -
    - - -

    - {{ "accountLimitReached" | i18n }} -

    -
    + + + + + + -
    - - -

    - {{ "options" | i18n }} -

    -
    + + + +
    + +
    - - - - - - - - - -
    -
    -
    -
    + + +

    {{ "availableAccounts" | i18n }}

    +
    - - -
    - -
    -
    {{ "switchAccounts" | i18n }}
    -
    +
    + +
    +
    +
    -
    - -
    -
    -
    -
    -
      - -
    • - -
    • - -
      - {{ "availableAccounts" | i18n }} -
      -
    • - -
    • -
      -
      -
    - -

    - {{ "accountLimitReached" | i18n }} -

    -
    +

    + {{ "accountLimitReached" | i18n }} +

    + + -
    -
    {{ "options" | i18n }}
    -
    - - - -
    -
    -
    -
    -
    +
    + + +

    + {{ "options" | i18n }} +

    +
    + + + + + + + + + + +
    +
    + diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index fb636ecaf6d..25e1b2ae83f 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -10,9 +10,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeou import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { UserId } from "@bitwarden/common/types/guid"; import { AvatarModule, @@ -25,7 +23,6 @@ import { import { enableAccountSwitching } from "../../../platform/flags"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { HeaderComponent } from "../../../platform/popup/header.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -44,7 +41,6 @@ import { AccountSwitcherService } from "./services/account-switcher.service"; AvatarModule, PopupPageComponent, PopupHeaderComponent, - HeaderComponent, PopOutComponent, CurrentAccountComponent, AccountComponent, @@ -58,7 +54,6 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { loading = false; activeUserCanLock = false; - extensionRefreshFlag = false; enableAccountSwitching = true; constructor( @@ -70,7 +65,6 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { private router: Router, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, private authService: AuthService, - private configService: ConfigService, private lockService: LockService, ) {} @@ -109,9 +103,6 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { async ngOnInit() { this.enableAccountSwitching = enableAccountSwitching(); - this.extensionRefreshFlag = await this.configService.getFeatureFlag( - FeatureFlag.ExtensionRefresh, - ); const availableVaultTimeoutActions = await firstValueFrom( this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(), diff --git a/apps/browser/src/auth/popup/account-switching/account.component.html b/apps/browser/src/auth/popup/account-switching/account.component.html index d062c67a2e3..d2e15d31899 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -1,109 +1,44 @@ - - - - - - - - - - - - + - - + + + + + diff --git a/apps/browser/src/auth/popup/account-switching/account.component.ts b/apps/browser/src/auth/popup/account-switching/account.component.ts index 5c4132e491c..104241e9c7b 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account.component.ts @@ -19,7 +19,6 @@ import { AccountSwitcherService, AvailableAccount } from "./services/account-swi }) export class AccountComponent { @Input() account: AvailableAccount; - @Input() extensionRefreshFlag: boolean = false; @Output() loading = new EventEmitter(); constructor( From c07454342d020bb8b9c775dbabf3dbad89eb047e Mon Sep 17 00:00:00 2001 From: Victoria League Date: Fri, 3 Jan 2025 09:41:18 -0500 Subject: [PATCH 051/270] [PM-16675] Prevent scrollbar from appearing on each send item (#12666) --- libs/components/src/item/item.component.ts | 2 +- .../send-list-items-container.component.html | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 833949ddb96..97a80484373 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -20,7 +20,7 @@ import { ItemActionComponent } from "./item-action.component"; providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }], host: { class: - "tw-block tw-box-border tw-overflow-auto tw-flex tw-bg-background [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-cursor-pointer [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", + "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-cursor-pointer [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", }, }) export class ItemComponent extends A11yRowDirective { 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 a0f6c09f83e..d7755546365 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 @@ -14,7 +14,6 @@ [queryParams]="{ sendId: send.id, type: send.type }" appStopClick type="button" - class="tw-pb-1" > Date: Fri, 3 Jan 2025 11:39:01 -0500 Subject: [PATCH 052/270] [deps] BRE: Update slackapi/slack-github-action action to v2 (#12140) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 63453b93838..3221c7eef2f 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1193,7 +1193,7 @@ jobs: if: | github.event_name != 'pull_request_target' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop') - uses: slackapi/slack-github-action@fcfb566f8b0aab22203f066d80ca1d7e4b5d05b3 # v1.27.1 + uses: slackapi/slack-github-action@485a9d42d3a73031f12ec201c457e2162c45d02d # v2.0.0 with: channel-id: C074F5UESQ0 payload: | From 1e6471bb1d92a461dde935fd769c4a4bb41ec5e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:57:54 -0500 Subject: [PATCH 053/270] [deps] Platform: Update Rust crate cc to 1.2.4 (#12297) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/objc/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 86c33d34843..384ee30b1c3 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.4" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" dependencies = [ "shlex", ] diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml index 1299e34a657..04e332b8db3 100644 --- a/apps/desktop/desktop_native/objc/Cargo.toml +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -17,5 +17,5 @@ tokio = "1.39.1" core-foundation = "=0.10.0" [build-dependencies] -cc = "1.0.104" +cc = "1.2.4" glob = "0.3.1" From c69278e7616f4d15f42b62070bfb2a4264daa6aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:28:44 -0500 Subject: [PATCH 054/270] [deps] Platform: Update Rust crate uniffi to 0.28.3 (#12549) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 8 ++++---- apps/desktop/desktop_native/macos_provider/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 384ee30b1c3..1933eefd224 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -1134,7 +1134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1519,7 +1519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -2836,7 +2836,7 @@ dependencies = [ "fastrand", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3340,7 +3340,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml index 28cc6372c62..ff7408d6d44 100644 --- a/apps/desktop/desktop_native/macos_provider/Cargo.toml +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -21,10 +21,10 @@ serde = { version = "1.0.205", features = ["derive"] } serde_json = "1.0.122" tokio = { version = "1.39.2", features = ["sync"] } tokio-util = "0.7.11" -uniffi = { version = "0.28.0", features = ["cli"] } +uniffi = { version = "0.28.3", features = ["cli"] } [target.'cfg(target_os = "macos")'.dependencies] oslog = "0.2.0" [build-dependencies] -uniffi = { version = "0.28.0", features = ["build"] } +uniffi = { version = "0.28.3", features = ["build"] } From 05d373d070bb25a882d1ed8de6ee6c5e3b18f58e Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 3 Jan 2025 14:51:54 -0500 Subject: [PATCH 055/270] turn off autocomplete for browser search input (#12679) --- .../vault-v2/vault-search/vault-v2-search.component.html | 1 + libs/components/src/search/search.component.html | 1 + libs/components/src/search/search.component.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html index 7cf154c0ee8..224eaccd93c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html @@ -1,4 +1,5 @@
    diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 6ec79eaa84e..9a811ce6777 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -46,6 +46,7 @@ export class SearchComponent implements ControlValueAccessor, FocusableElement { @Input() disabled: boolean; @Input() placeholder: string; + @Input() autocomplete: string; getFocusTarget() { return this.input.nativeElement; From dac23db9524d1c5cc8385018479bbf6bc5afd1c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:15:32 -0500 Subject: [PATCH 056/270] [deps] Platform: Update Rust crate napi-build to v2.1.4 (#12546) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/napi/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 1933eefd224..b40246fca2d 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -1667,9 +1667,9 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" +checksum = "db836caddef23662b94e16bf1f26c40eceb09d6aee5d5b06a7ac199320b69b19" [[package]] name = "napi-derive" diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index 6a656cdc574..8e19d62c1e6 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -30,4 +30,4 @@ tokio-stream = "=0.1.15" windows-registry = "=0.3.0" [build-dependencies] -napi-build = "=2.1.3" +napi-build = "=2.1.4" From 196c1e1fa4189946d14b5aea468fb7d6aa94ed6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:36:43 +0000 Subject: [PATCH 057/270] Fix installation ID missing in events by returning an object with the 'name' property instead of a string (#12463) --- .../admin-console/organizations/manage/events.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index c9fb1cb08f0..ab7306d9140 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -131,7 +131,9 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe protected getUserName(r: EventResponse, userId: string) { if (r.installationId != null) { - return `Installation: ${r.installationId}`; + return { + name: `Installation: ${r.installationId}`, + }; } if (userId != null) { From d34888a5683d91d9c0c4dccedf44447cda82789a Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:31:19 +0100 Subject: [PATCH 058/270] Autosync the updated translations (#12672) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 55 +++++ apps/web/src/locales/ar/messages.json | 55 +++++ apps/web/src/locales/az/messages.json | 55 +++++ apps/web/src/locales/be/messages.json | 55 +++++ apps/web/src/locales/bg/messages.json | 55 +++++ apps/web/src/locales/bn/messages.json | 55 +++++ apps/web/src/locales/bs/messages.json | 55 +++++ apps/web/src/locales/ca/messages.json | 55 +++++ apps/web/src/locales/cs/messages.json | 55 +++++ apps/web/src/locales/cy/messages.json | 55 +++++ apps/web/src/locales/da/messages.json | 55 +++++ apps/web/src/locales/de/messages.json | 55 +++++ apps/web/src/locales/el/messages.json | 55 +++++ apps/web/src/locales/en_GB/messages.json | 55 +++++ apps/web/src/locales/en_IN/messages.json | 55 +++++ apps/web/src/locales/eo/messages.json | 55 +++++ apps/web/src/locales/es/messages.json | 55 +++++ apps/web/src/locales/et/messages.json | 55 +++++ apps/web/src/locales/eu/messages.json | 55 +++++ apps/web/src/locales/fa/messages.json | 55 +++++ apps/web/src/locales/fi/messages.json | 55 +++++ apps/web/src/locales/fil/messages.json | 55 +++++ apps/web/src/locales/fr/messages.json | 55 +++++ apps/web/src/locales/gl/messages.json | 55 +++++ apps/web/src/locales/he/messages.json | 55 +++++ apps/web/src/locales/hi/messages.json | 55 +++++ apps/web/src/locales/hr/messages.json | 55 +++++ apps/web/src/locales/hu/messages.json | 55 +++++ apps/web/src/locales/id/messages.json | 55 +++++ apps/web/src/locales/it/messages.json | 55 +++++ apps/web/src/locales/ja/messages.json | 55 +++++ apps/web/src/locales/ka/messages.json | 55 +++++ apps/web/src/locales/km/messages.json | 55 +++++ apps/web/src/locales/kn/messages.json | 55 +++++ apps/web/src/locales/ko/messages.json | 55 +++++ apps/web/src/locales/lv/messages.json | 55 +++++ apps/web/src/locales/ml/messages.json | 55 +++++ apps/web/src/locales/mr/messages.json | 55 +++++ apps/web/src/locales/my/messages.json | 55 +++++ apps/web/src/locales/nb/messages.json | 55 +++++ apps/web/src/locales/ne/messages.json | 55 +++++ apps/web/src/locales/nl/messages.json | 255 ++++++++++++++--------- apps/web/src/locales/nn/messages.json | 55 +++++ apps/web/src/locales/or/messages.json | 55 +++++ apps/web/src/locales/pl/messages.json | 55 +++++ apps/web/src/locales/pt_BR/messages.json | 55 +++++ apps/web/src/locales/pt_PT/messages.json | 55 +++++ apps/web/src/locales/ro/messages.json | 55 +++++ apps/web/src/locales/ru/messages.json | 55 +++++ apps/web/src/locales/si/messages.json | 55 +++++ apps/web/src/locales/sk/messages.json | 55 +++++ apps/web/src/locales/sl/messages.json | 55 +++++ apps/web/src/locales/sr/messages.json | 101 +++++++-- apps/web/src/locales/sr_CS/messages.json | 55 +++++ apps/web/src/locales/sv/messages.json | 55 +++++ apps/web/src/locales/te/messages.json | 55 +++++ apps/web/src/locales/th/messages.json | 55 +++++ apps/web/src/locales/tr/messages.json | 69 +++++- apps/web/src/locales/uk/messages.json | 55 +++++ apps/web/src/locales/vi/messages.json | 55 +++++ apps/web/src/locales/zh_CN/messages.json | 87 ++++++-- apps/web/src/locales/zh_TW/messages.json | 55 +++++ 62 files changed, 3556 insertions(+), 146 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 2e8c1fdea10..f86b6ab497b 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 91a223bc83f..ed843a62cd1 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 1b3685420ce..09785738464 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Güncəlllənən vergi məlumatı" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Doğrulanmayıb" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Təşkilat adı 50 xarakterdən çox ola bilməz." + }, + "resellerRenewalWarning": { + "message": "Abunəliyiniz tezliklə yenilənəcək. Kəsintisiz xidməti təmin etmək və yeniləməni $RENEWAL_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Abunəliyinizə aid faktura $ISSUED_DATE$ tarixində təqdim edildi. Kəsintisiz xidməti təmin etmək və yeniləməni $DUE_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Abunəliyinizə aid faktura üzrə ödəniş edilmədi. Kəsintisiz xidməti təmin etmək və yeniləməni $GRACE_PERIOD_END$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 335dcbbe650..ea705b38b4e 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 4a0f650bd3f..1f5fb8d8c7e 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Обновена данъчна информация" }, + "billingInvalidTaxIdError": { + "message": "Неправилен данъчен идентификатор. Ако смятате, че това е грешка, свържете се с поддръжката." + }, + "billingTaxIdTypeInferenceError": { + "message": "Не успяхме да потвърдим Вашия данъчен идентификатор. Ако смятате, че това е грешка, свържете се с поддръжката." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Неправилен данъчен идентификатор. Ако смятате, че това е грешка, свържете се с поддръжката." + }, + "billingPreviewInvoiceError": { + "message": "Възникна грешка при преглеждането на фактурата. Опитайте отново по-късно." + }, "unverified": { "message": "Непотвърден" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Името на организацията не може да бъде по-дълго от 50 знака." + }, + "resellerRenewalWarning": { + "message": "Вашият абонамент ще бъде подновен скоро. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Фактура за абонамента Ви беше издадена на $ISSUED_DATE$. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Фактурата за абонамента Ви не е била платена. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 3860a66b925..56a04f3d870 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 1604eb20677..3e9c4525b04 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 38684e8aefa..1cf16dcc0fd 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 0fdac8e60f3..64ce764de82 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Aktualizované daňové údaje" }, + "billingInvalidTaxIdError": { + "message": "Neplatné DIČ. Pokud se domníváte, že se jedná o chybu, kontaktujte podporu." + }, + "billingTaxIdTypeInferenceError": { + "message": "Nebyli jsme schopni ověřit Vaše DIČ. Pokud se domníváte, že se jedná o chybu, kontaktujte podporu." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Neplatné DIČ. Pokud se domníváte, že se jedná o chybu, kontaktujte podporu." + }, + "billingPreviewInvoiceError": { + "message": "Při náhledu faktury došlo k chybě. Opakujte akci později." + }, "unverified": { "message": "Neověřeno" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Název organizace nesmí přesáhnout 50 znaků." + }, + "resellerRenewalWarning": { + "message": "Vaše předplatné se brzy obnoví. Chcete-li zajistit nepřerušenou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Faktura pro Vaše předplatné byla vystavena dne $ISSUED_DATE$. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Faktura za Vaše předplatné nebyla zaplacena. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index c774497eae2..c0c39c91d30 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 86bb1145217..b2b3b0491c3 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Opdaterede momsoplysninger" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Ubekræftet" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organisationsnavn må ikke overstige 50 tegn." + }, + "resellerRenewalWarning": { + "message": "Abonnementet fornyes snart. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "En faktura for abonnementet er udstedt pr. $ISSUED_DATE$. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Fakturaen for abonnementet er ikke blevet betalt. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 4ed75be054f..4596dd88b3a 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Steuerinformationen aktualisiert" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Nicht verifiziert" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Der Name der Organisation darf 50 Zeichen nicht überschreiten." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 50a327b1fa1..42d24fae72d 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Τα φορολογικά στοιχεία ενημερώθηκαν" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 3da937ec266..f37aa8150cf 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organisation name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index dd507896e56..131b6b36383 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organisation name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 06225dba76f..0170c225c03 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index f2548036c2a..3c3d7bb135d 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 21096b3f710..700c748add8 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 19de255284b..4c7719ada13 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index bb422bf70ae..fe0ba063a7b 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index a5d576d349d..234cfb0ee3e 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Verotiedot muutettiin" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Vahvistamaton" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 6257f2b5eaf..f5a9b078984 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index c9f87b19267..e6c402293f8 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Informations sur les taxes mises à jour" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Non vérifié" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Le nom de l'organisation ne doit pas dépasser 50 caractères." + }, + "resellerRenewalWarning": { + "message": "Votre abonnement sera renouvelé bientôt. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Une facture pour votre abonnement a été émise sur $ISSUED_DATE$. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "La facture de votre abonnement n'a pas été payée. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 7fd7be8395d..986350b0637 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 32e51d6a037..9c4ef530721 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 3f2bd5897e6..82a0c12a390 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 5a397cd4b14..7b77ae9d58f 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Ažurirane porezne informacije" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Nepotvrđeno" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Naziv organizacije ne može biti duži od 50 znakova." + }, + "resellerRenewalWarning": { + "message": "Tvoja će se pretplata uskoro obnoviti. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Za tvoju je pretplatu $ISSUED_DATE$ izdana faktura. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Faktura za tvoju pretplatu nije plaćena. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnovu prije $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 682f080fb87..ecd7db04ca1 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Frissített adó információ" }, + "billingInvalidTaxIdError": { + "message": "Érvénytelen az adóazonosító. Ha úgy gondoljuk, hogy ez hiba, forduljunk az ügyfélszolgálathoz." + }, + "billingTaxIdTypeInferenceError": { + "message": "Nem lehetett ellenőrizni az adóazonosítót. Ha úgy gondoljuk, hogy ez hiba, forduljunk az ügyfélszolgálathoz." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Érvénytelen az adóazonosító. Ha úgy gondoljuk, hogy ez hiba, forduljunk az ügyfélszolgálathoz." + }, + "billingPreviewInvoiceError": { + "message": "Hiba történt a számla előnézete közben. Próbáljuk újra később." + }, "unverified": { "message": "Nem ellenőrzött" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "A szervezet neve nem haladhatja meg az 50 karaktert." + }, + "resellerRenewalWarning": { + "message": "Az előfizetés hamarosan megújul. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $RENEWAL_DATE$ előtt.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Az előfizetésről szóló számla kiállítása $ISSUED_DATE$ napon történt. A megszakítás nélküli szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $DUE_DATE$ előtt.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Az előfizetésről szóló számla nem lett kfizetve. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $GRACE_PERIOD_END$ előtt.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 877a6b21261..7517cac5ae5 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 36bcc07a023..0154a3c8c78 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Informazioni fiscali aggiornate" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Non verificato" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 343818e3ad4..1383ce49170 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "更新された税情報" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "未認証" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 2a61cd89766..cc4dc222103 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index c5a77826c9e..36ab0050700 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index ec7241be790..e1fd4cc9ac9 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 59c0e15ee33..7966191f530 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 8f8ae16320c..6546b248914 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Atjaunināta nodokļu informācija" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Neapliecināts" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Apvienības nosaukums nevar pārsniegt 50 rakstzīmes." + }, + "resellerRenewalWarning": { + "message": "Abonements drīz tiks atjaunots. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $RENEWAL_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Rēķins par abonementu tika izdots $ISSUED_DATE$. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $DUE_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Rēķins par abonementu nav apmaksāts. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $GRACE_PERIOD_END$ jāsazināš ar $RESELLER$, lai apstiprinātu atjaunošanu.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index bca6a172b62..c6f9aa8d853 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index c5a77826c9e..36ab0050700 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index c5a77826c9e..36ab0050700 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index caf22428259..af26fc4df94 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 345b8945460..e51fb3ced67 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 4e6e29704b6..cd72a94251c 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -952,7 +952,7 @@ "message": "Uitgelogd" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Je bent afgemeld bij je account." }, "loginExpired": { "message": "Je inlogsessie is verlopen." @@ -1126,7 +1126,7 @@ "message": "De verificatiesessie is verlopen. Start het inlogproces opnieuw op." }, "verifyIdentity": { - "message": "Verify your Identity" + "message": "Controleer je identiteit" }, "logInInitiated": { "message": "Inloggen gestart" @@ -1254,7 +1254,7 @@ "message": "E-mailadres" }, "yourVaultIsLockedV2": { - "message": "Je kluis is vergrendeld." + "message": "Je kluis is vergrendeld" }, "yourAccountIsLocked": { "message": "Je account is vergrendeld" @@ -1450,7 +1450,7 @@ "message": "Wijzig de verzamelingen waarmee dit item gedeeld is. Alleen organisatiegebruikers met toegang tot deze verzamelingen kunnen dit item inzien." }, "deleteSelectedItemsDesc": { - "message": "Je hebt $COUNT$ item(s) geselecteerd om te verwijderen. Weet je zeker dat je al deze items wilt verwijderen?", + "message": "$COUNT$ item(s) worden naar de prullenbak gestuurd.", "placeholders": { "count": { "content": "$1", @@ -1471,7 +1471,7 @@ "message": "Weet je zeker dat je wilt doorgaan?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Kies een map waaraan je de $COUNT$ geselecteerde item(s) wilt toevoegen.", "placeholders": { "count": { "content": "$1", @@ -1506,10 +1506,10 @@ "message": "UUID kopiëren" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Fout bij vernieuwen toegangstoken" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Geen verversingstoken of API-sleutels gevonden. Probeer uit te loggen en weer in te loggen." }, "warning": { "message": "Waarschuwing" @@ -1590,7 +1590,7 @@ "message": "Dit bestand is beveiligd met een wachtwoord. Voer het bestandswachtwoord in om gegevens te importeren." }, "exportSuccess": { - "message": "Je kluisgegevens zijn geëxporteerd." + "message": "Kluisgegevens geëxporteerd" }, "passwordGenerator": { "message": "Wachtwoordgenerator" @@ -1870,11 +1870,11 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { - "message": " aanmaken.", + "message": " in plaats daarvan.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." }, "onboardingImportDataDetailsPartTwoWithOrgs": { - "message": " aanmaken. Je moet misschien wachten tot je beheerder je organisatielidmaatschap bevestigt.", + "message": " in plaats daarvan. Je moet misschien wachten tot je beheerder je organisatielidmaatschap bevestigt.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -1884,7 +1884,7 @@ "message": "Er was een probleem met de data die je probeerde te importeren. Los de onderstaande fouten op in het bronbestand en probeer het opnieuw." }, "importSuccess": { - "message": "De gegevens zijn in je kluis geïmporteerd." + "message": "Gegevens succesvol geïmporteerd" }, "importSuccessNumberOfItems": { "message": "Een totaal van $AMOUNT$ items zijn geïmporteerd.", @@ -2752,7 +2752,7 @@ "message": "Weet je zeker dat je wilt opzeggen? Je verliest toegang tot alle functionaliteiten van dit abonnement aan het einde van deze betalingscyclus." }, "canceledSubscription": { - "message": "Het abonnement is opgezegd." + "message": "Abonnement geannuleerd" }, "neverExpires": { "message": "Vervalt nooit" @@ -3138,7 +3138,7 @@ "message": "Je nieuwe organisatie is klaar voor gebruik!" }, "organizationUpgraded": { - "message": "Je organisatie is bijgewerkt." + "message": "Organisatie bijgewerkt" }, "leave": { "message": "Verlaten" @@ -3147,7 +3147,7 @@ "message": "Weet je zeker dat je deze organisatie wilt verlaten?" }, "leftOrganization": { - "message": "Je hebt de organisatie verlaten." + "message": "Je hebt de organisatie verlaten" }, "defaultCollection": { "message": "Standaardverzameling" @@ -3285,7 +3285,7 @@ "message": "Eigenaar" }, "ownerDesc": { - "message": "De gebruiker met de hoogste toegangsrechten. Deze gebruiker kan alle aspecten van je organisatie beheren." + "message": "De gebruiker met de hoogste toegangsrechten. Deze gebruiker kan alle aspecten van je organisatie beheren" }, "clientOwnerDesc": { "message": "Deze gebruiker moet onafhankelijk zijn van de provider. Als de provider is losgekoppeld van de organisatie, blijft deze gebruiker eigenaar van de organisatie." @@ -3294,22 +3294,22 @@ "message": "Beheerder" }, "adminDesc": { - "message": "Beheerders hebben toegang tot alle items, verzamelingen en gebruikers binnen je organisatie en kunnen deze ook beheren." + "message": "Beheerders hebben toegang tot alle items, verzamelingen en gebruikers binnen je organisatie en kunnen deze ook beheren" }, "user": { "message": "Gebruiker" }, "userDesc": { - "message": "Een standaardgebruiker met toegang tot de verzamelingen van je organisatie." + "message": "Items openen en toevoegen aan toegewezen collecties" }, "all": { "message": "Alle" }, "addAccess": { - "message": "Add Access" + "message": "Toegang toevoegen" }, "addAccessFilter": { - "message": "Add Access Filter" + "message": "Toegangsfilter toevoegen" }, "refresh": { "message": "Verversen" @@ -3351,16 +3351,16 @@ "message": "Bitwarden Secrets Manager" }, "loggedIn": { - "message": "Ingelogd." + "message": "Ingelogd" }, "changedPassword": { - "message": "Accountwachtwoord veranderd." + "message": "Accountwachtwoord veranderd" }, "enabledUpdated2fa": { - "message": "Tweestapsaanmelding geactiveerd/bijgewerkt." + "message": "Inloggen in twee stappen opgeslagen" }, "disabled2fa": { - "message": "Tweestapsaanmelding uitgeschakeld." + "message": "Inloggen in twee stappen uitgeschakeld" }, "recovered2fa": { "message": "Account hersteld van tweestapsaanmelding." @@ -3385,7 +3385,7 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "exportedVault": { - "message": "Kluis geëxporteerd." + "message": "Kluis geëxporteerd" }, "exportedOrganizationVault": { "message": "Organisatiekluis geëxporteerd." @@ -3808,7 +3808,7 @@ "message": "Wijzig de groep waar deze gebruiker bij hoort." }, "invitedUsers": { - "message": "Gebruiker(s) uitgenodigd." + "message": "Gebruiker(s) uitgenodigd" }, "resendInvitation": { "message": "Uitnodiging opnieuw versturen" @@ -3817,7 +3817,7 @@ "message": "E-mail opnieuw versturen" }, "hasBeenReinvited": { - "message": "$USER$ is opnieuw uitgenodigd.", + "message": "$USER$ opnieuw uitgenodigd", "placeholders": { "user": { "content": "$1", @@ -3865,7 +3865,7 @@ "message": "Kijk in het postvak IN van je e-mail voor een verificatielink." }, "emailVerified": { - "message": "Je e-mailadres is geverifieerd." + "message": "Account e-mail geverifieerd" }, "emailVerifiedV2": { "message": "E-mailadres geverifieerd" @@ -4089,7 +4089,7 @@ "message": "Als je de bankrekening niet verifieert mis je een betaling waardoor je abonnement wordt uitgeschakeld." }, "verifiedBankAccount": { - "message": "Bankrekening geverifieerd." + "message": "Bankrekening geverifieerd" }, "bankAccount": { "message": "Bankrekening" @@ -4181,10 +4181,10 @@ "message": "Aanpassingen aan je abonnement leiden tot evenredige wijzigingen in je factuurtotaal. Als nieuwe gebruikers je gebruikersplaatsen overschrijden, ontvang je onmiddellijk een afschrijving voor de extra gebruikers." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "Als je aanvullende" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { - "message": "seats without the bundled offer, please contact" + "message": "plaatsen zonder de gebundelde aanbieding, neem dan contact op met" }, "subscriptionUserSeatsLimitedAutoscale": { "message": "Aanpassingen aan je abonnement leiden tot evenredige wijzigingen in je factuurtotaal. Als nieuwe gebruikers je gebruikersplaatsen overschrijden, ontvang je onmiddellijk een afschrijving voor de extra gebruikers tot het aantal van $MAX$ gebruikersplaatsen is bereikt.", @@ -4394,7 +4394,7 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "Organisatie uitgeschakeld." + "message": "Organisatie opgeschort" }, "secretsAccessSuspended": { "message": "Opgeschorte organisaties zijn niet toegankelijk. Neem contact op met de eigenaar van je organisatie voor hulp." @@ -4662,7 +4662,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Definitief verwijderd item $ID$.", + "message": "Item $ID$ permanent verwijderd", "placeholders": { "id": { "content": "$1", @@ -4683,7 +4683,7 @@ "message": "Herstelde items" }, "restoredItemId": { - "message": "Hersteld item $ID$.", + "message": "Item $ID$ hersteld", "placeholders": { "id": { "content": "$1", @@ -5102,7 +5102,7 @@ } }, "emergencyApproved": { - "message": "Noodtoegang goedgekeurd." + "message": "Noodtoegang goedgekeurd" }, "emergencyRejected": { "message": "Noodtoegang afgewezen" @@ -5194,7 +5194,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseLink": { - "message": "Enterprise feature", + "message": "enterprise functie", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Custom roles is an enterprise feature. Contact our support team to upgrade your subscription'" }, "customDescNonEnterpriseEnd": { @@ -5533,7 +5533,7 @@ "message": "Wachtwoord opnieuw ingesteld!" }, "resetPasswordEnrollmentWarning": { - "message": "Inschrijving stelt organisatiebeheerders in staat om je hoofdwachtwoord te wijzigen. Weet je zeker dat je wilt inschrijven?" + "message": "Registratie geeft organisatiebeheerders de mogelijkheid om je hoofdwachtwoord te wijzigen" }, "accountRecoveryPolicy": { "message": "Accountherstel-administratie" @@ -5617,10 +5617,10 @@ "message": "Bulkactie status" }, "bulkConfirmMessage": { - "message": "Succesvol bevestigd." + "message": "Succesvol bevestigd" }, "bulkReinviteMessage": { - "message": "Succesvol opnieuw uitgenodigd." + "message": "Succesvol opnieuw uitgenodigd" }, "bulkRemovedMessage": { "message": "Succesvol verwijderd" @@ -5632,7 +5632,7 @@ "message": "Toegang tot de organisatie hersteld" }, "bulkFilteredMessage": { - "message": "Uitgezonderd, niet van toepassing voor deze actie." + "message": "Uitgesloten, niet van toepassing op deze actie" }, "nonCompliantMembersTitle": { "message": "Niet-conforme leden" @@ -5671,7 +5671,7 @@ "message": "Providernaam" }, "providerSetup": { - "message": "De provider is ingesteld." + "message": "Provider succesvol ingesteld" }, "clients": { "message": "Apparaten" @@ -5757,7 +5757,7 @@ } }, "providerIsDisabled": { - "message": "Provider is uitgeschakeld." + "message": "Aanbieder geschorst" }, "providerUpdated": { "message": "Provider bijgewerkt" @@ -5835,7 +5835,7 @@ "message": "Minuten" }, "vaultTimeoutPolicyInEffect": { - "message": "Het beleid van je organisatie heeft invloed op de time-out van je kluis. De maximaal toegestane time-out voor je kluis is $HOURS$ uur en $MINUTES$ minuten", + "message": "Het beleid van je organisatie heeft invloed op de time-out van je kluis. De maximaal toegestane time-out voor je kluis is $HOURS$ uur en $MINUTES$ minuten.", "placeholders": { "hours": { "content": "$1", @@ -6037,7 +6037,7 @@ "message": "Onderteken authenticatie aanvragen" }, "ssoSettingsSaved": { - "message": "Single Sign-On configuratie is opgeslagen." + "message": "Single sign-on configuratie opgeslagen" }, "sponsoredFamilies": { "message": "Gratis Bitwarden Families" @@ -6196,7 +6196,7 @@ "message": "Hoofdwachtwoord verwijderen" }, "removedMasterPassword": { - "message": "Hoofdwachtwoord verwijderd." + "message": "Hoofdwachtwoord verwijderd" }, "allowSso": { "message": "SSO-authenticatie toestaan" @@ -6319,37 +6319,37 @@ "message": "Het roteren van het factureringssynchronisatietoken maakt het vorige token ongeldig." }, "selfHostedServer": { - "message": "self-hosted" + "message": "zelf gehost" }, "customEnvironment": { - "message": "Custom environment" + "message": "Aangepaste omgeving" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "Geef de basis URL op van je on-premises gehoste Bitwarden installatie. Voorbeeld: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Voor geavanceerde configuratie kun je de basis URL van elke service onafhankelijk opgeven." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Je moet de basis URL van de server of ten minste één aangepaste omgeving toevoegen." }, "apiUrl": { - "message": "API server URL" + "message": "API-server URL" }, "webVaultUrl": { - "message": "Web vault server URL" + "message": "Webkluisserver URL" }, "identityUrl": { - "message": "Identity server URL" + "message": "Identiteitsserver URL" }, "notificationsUrl": { - "message": "Notifications server URL" + "message": "Meldingen server URL" }, "iconsUrl": { - "message": "Icons server URL" + "message": "Pictogrammen server URL" }, "environmentSaved": { - "message": "Environment URLs saved" + "message": "Omgevings-URL's opgeslagen" }, "selfHostingTitle": { "message": "Zelfgehost" @@ -6358,7 +6358,7 @@ "message": "Voor het instellen van je organisatie op je eigen server, moet je je licentiebestand uploaden. Om gratis Families-plannen en geavanceerde factureringsmogelijkheden voor je zelfgehoste organisatie te ondersteunen, moet je factureringssynchronisatie instellen." }, "billingSyncApiKeyRotated": { - "message": "Token geroteerd." + "message": "Token geroteerd" }, "billingSyncKeyDesc": { "message": "Er is een factureringssynchronisatietoken van de abonnementsinstellingen van je cloudorganisatie vereist voor het afronden van dit formulier." @@ -6679,7 +6679,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ fout: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -6693,11 +6693,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Gegenereerd door Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Website: $WEBSITE$. Gegenereerd door Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -6707,7 +6707,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ongeldig $SERVICENAME$ API token", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -6717,7 +6717,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ongeldige $SERVICENAME$ API token: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -6731,7 +6731,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Kan $SERVICENAME$ gemaskeerde e-mailaccount-ID niet verkrijgen.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -6741,7 +6741,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ongeldig $SERVICENAME$ domein.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -6751,7 +6751,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Ongeldige $SERVICENAME$ url.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -6761,7 +6761,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Onbekende $SERVICENAME$ fout opgetreden.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -6771,7 +6771,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Onbekende doorstuurder: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -7478,7 +7478,7 @@ "message": "Geef toegang tot collecties door ze aan deze groep toe te voegen." }, "restrictedCollectionAssignmentDesc": { - "message": "You can only assign collections you manage." + "message": "Je kunt alleen verzamelingen toewijzen die je beheert." }, "selectMembers": { "message": "Leden selecteren" @@ -7700,7 +7700,7 @@ "message": "Groepen selecteren" }, "userPermissionOverrideHelperDesc": { - "message": "Permissions set for a member will replace permissions set by that member's group." + "message": "Rechten ingesteld voor een lid vervangen de rechten ingesteld door de groep van dat lid." }, "noMembersOrGroupsAdded": { "message": "Geen leden of groepen toegevoegd" @@ -7904,7 +7904,7 @@ "message": "Werk je versleutelingsinstellingen bij om aan de nieuwe beveiligingsaanbevelingen te voldoen en de bescherming van je account te verbeteren." }, "kdfSettingsChangeLogoutWarning": { - "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." + "message": "Als je doorgaat, log je uit van alle actieve sessies. Je zult opnieuw moeten inloggen en, indien van toepassing, tweestapsverificatie moeten voltooien. We raden aan om je kluis te exporteren voordat je je versleutelingsinstellingen wijzigt om gegevensverlies te voorkomen." }, "secretsManager": { "message": "Secrets Manager" @@ -8559,10 +8559,10 @@ "message": "Je hebt geen toegang om deze collectie te beheren." }, "grantAddAccessCollectionWarningTitle": { - "message": "Missing Can Manage Permissions" + "message": "Ontbrekende Kan beheren machtigingen" }, "grantAddAccessCollectionWarning": { - "message": "Grant Can manage permissions to allow full collection management including deletion of collection." + "message": "Kan beheren machtigingen verlenen voor volledig verzamelingsbeheer, inclusief het verwijderen van verzamelingen." }, "grantCollectionAccess": { "message": "Groepen of mensen toegang tot deze collectie geven." @@ -8660,7 +8660,7 @@ "message": "Het is niet mogelijk om jezelf toe te voegen aan groepen." }, "cannotAddYourselfToCollections": { - "message": "You cannot add yourself to collections." + "message": "Je kunt jezelf niet toevoegen aan verzamelingen." }, "assign": { "message": "Toewijzen" @@ -9112,25 +9112,25 @@ } }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "Maak een nieuwe clientorganisatie aan om te beheren als Aanbieder. Extra plaatsen worden weergegeven in de volgende factureringscyclus." }, "selectAPlan": { - "message": "Select a plan" + "message": "Selecteer een plan" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% korting" }, "monthPerMember": { - "message": "month per member" + "message": "maand per lid" }, "seats": { - "message": "Seats" + "message": "Personen" }, "addOrganization": { - "message": "Add organization" + "message": "Organisatie toevoegen" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "Nieuwe klant succesvol aangemaakt" }, "noAccess": { "message": "Geen toegang" @@ -9139,16 +9139,16 @@ "message": "Deze collectie is alleen toegankelijk vanaf de admin console" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "Organisatiemenu togglen" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Kluisitem selecteren" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Verzamelitem selecteren" }, "manageBillingFromProviderPortalMessage": { - "message": "Manage billing from the Provider Portal" + "message": "Facturering beheren vanuit het aanbiederportaal" }, "continueSettingUpFreeTrial": { "message": "Doorgaan met het instellen van je gratis proefperiode van Bitwarden" @@ -9169,7 +9169,7 @@ "message": "Voer je organisatie-informatie voor Enterprise in" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Bekijk items in $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -9179,7 +9179,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Terug naar $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -9189,11 +9189,11 @@ } }, "back": { - "message": "Back", + "message": "Terug", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ verwijderen", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -9203,13 +9203,13 @@ } }, "viewInfo": { - "message": "View info" + "message": "Bekijk info" }, "viewAccess": { - "message": "View access" + "message": "Toegang bekijken" }, "noCollectionsSelected": { - "message": "You have not selected any collections." + "message": "Je hebt geen verzamelingen geselecteerd." }, "updateName": { "message": "Naam bijwerken" @@ -9218,7 +9218,7 @@ "message": "Organisatienaam bijgewerkt" }, "providerPlan": { - "message": "Managed Service Provider" + "message": "Beheerde dienstaanbieder" }, "managedServiceProvider": { "message": "Managed service provider" @@ -9227,10 +9227,10 @@ "message": "Multi-organisatie onderneming" }, "orgSeats": { - "message": "Organization Seats" + "message": "Organisatie plaatsen" }, "providerDiscount": { - "message": "$AMOUNT$% Discount", + "message": "$AMOUNT$% korting", "placeholders": { "amount": { "content": "$1", @@ -9239,7 +9239,7 @@ } }, "lowKDFIterationsBanner": { - "message": "Laag aantal KDF-iteraties. Verhoog je iteraties om de veiligheid van je account te verbeteren," + "message": "Laag aantal KDF-iteraties. Verhoog je iteraties om de veiligheid van je account te verbeteren." }, "changeKDFSettings": { "message": "KDF-instellingen wijzigen" @@ -9251,10 +9251,10 @@ "message": "Bescherm je gezin of bedrijf" }, "upgradeOrganizationCloseSecurityGaps": { - "message": "Close security gaps with monitoring reports" + "message": "Beveiligingslekken dichten met bewakingsrapporten" }, "upgradeOrganizationCloseSecurityGapsDesc": { - "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + "message": "Blijf kwetsbaarheden in de beveiliging voor door te upgraden naar een betaald plan voor verbeterde monitoring." }, "approveAllRequests": { "message": "Alle verzoeken goedkeuren" @@ -9269,13 +9269,25 @@ "message": "Bitcoin" }, "updatedTaxInformation": { - "message": "Updated tax information" + "message": "Bijgewerkte belastinggegevens" + }, + "billingInvalidTaxIdError": { + "message": "Ongeldig btw-nummer, als je denkt dat dit een fout is, neem dan contact op met support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We konden je btw-nummer niet valideren, als je denkt dat dit een fout is, neem dan contact op met support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Ongeldig btw-nummer, als je denkt dat dit een fout is, neem dan contact op met support." + }, + "billingPreviewInvoiceError": { + "message": "Er is een fout opgetreden met het weergeven van de factuur. Probeer het later nog eens." }, "unverified": { - "message": "Unverified" + "message": "Niet-geverifieerd" }, "verified": { - "message": "Verified" + "message": "Geverifieerd" }, "viewSecret": { "message": "Geheim weergeven" @@ -9698,7 +9710,7 @@ "message": "Je Secrets Manager-abonnement zal upgraden naar het geselecteerde abonnement" }, "bitwardenPasswordManager": { - "message": "Bitwarden Password Manager" + "message": "Bitwarden Wachtwoordbeheerder" }, "secretsManagerComplimentaryPasswordManager": { "message": "Je gratis eenjarige Password Manager-abonnement zal veranderen naar het geselecteerde abonnement. Er worden pas kosten in rekening gebracht als de gratis periode voorbij is." @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organisatienaam mag niet langer zijn dan 50 tekens." + }, + "resellerRenewalWarning": { + "message": "Je abonnement wordt binnenkort verlengd. Neem voor $RENEWAL_DATE$ contact op met $RESELLER$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Er is een factuur voor je abonnement aangemaakt op $ISSUED_DATE$. Neem contact op met $RESELLER$ voor $DUE_DATE$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "De factuur voor je abonnement is niet betaald. Neem contact op met $RESELLER$ voor $GRACE_PERIOD_END$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 7951e875fe8..0e3c134ba4d 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index c5a77826c9e..36ab0050700 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 67d63ce9b33..58e4de95317 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Zaktualizowane informacje podatkowe" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Niezweryfikowane" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 5461370c663..463b7f5e060 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Informações fiscais atualizadas" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Não verificado" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 28b65826f02..57a2eee7cf5 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Informações fiscais atualizadas" }, + "billingInvalidTaxIdError": { + "message": "Número de identificação fiscal inválido. Se considerar que se trata de um erro, contacte a assistência." + }, + "billingTaxIdTypeInferenceError": { + "message": "Não foi possível validar o seu número de identificação fiscal. Se considerar que se trata de um erro, contacte a assistência." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Número de identificação fiscal inválido. Se considerar que se trata de um erro, contacte a assistência." + }, + "billingPreviewInvoiceError": { + "message": "Ocorreu um erro ao pré-visualizar a fatura. Por favor, tente novamente mais tarde." + }, "unverified": { "message": "Não verificado" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "O nome da organização não pode exceder 50 caracteres." + }, + "resellerRenewalWarning": { + "message": "A sua subscrição será renovada em breve. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "A fatura da sua subscrição foi emitida a $ISSUED_DATE$. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "A fatura da sua subscrição não foi paga. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 5fdb6ab5e68..18ff0b2158f 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index fd309722f46..89209f2fa52 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Обновление сведений о налогах" }, + "billingInvalidTaxIdError": { + "message": "Недействительный ID, если вы считаете, что это ошибка, обратитесь в службу поддержки." + }, + "billingTaxIdTypeInferenceError": { + "message": "Мы не смогли подтвердить ваш ID, если вы считаете, что это ошибка, обратитесь в службу поддержки." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Недействительный ID, если вы считаете, что это ошибка, обратитесь в службу поддержки." + }, + "billingPreviewInvoiceError": { + "message": "При подготовке счета произошла ошибка. Пожалуйста, повторите попытку позже." + }, "unverified": { "message": "Неверифицирован" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Название организации не может превышать 50 символов." + }, + "resellerRenewalWarning": { + "message": "Ваша подписка скоро будет продлена. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Счет за вашу подписку был выставлен $ISSUED_DATE$. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$, чтобы подтвердить продление подписки до $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Счет за вашу подписку не был оплачен. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 3dfac52754f..0649f83f519 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 947e459b8fd..4c47afaa63d 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Aktualizované daňové informácie" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Neoverený" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Meno organizácie nemôže mať viac ako 50 znakov." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 55aceaecf2b..9a9535aebaa 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 17e82485f8c..e15d6d66653 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -3889,7 +3889,7 @@ "message": "Користите неподржани веб прегледач. Веб сеф можда неће правилно функционисати." }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "Ваша проба се завршава за $COUNT$ дана.", "placeholders": { "count": { "content": "$1", @@ -3898,7 +3898,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$, Ваша проба са завршава за $COUNT$ дана.", "placeholders": { "count": { "content": "$2", @@ -3911,7 +3911,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$, Ваша проба са завршава сутра.", "placeholders": { "organization": { "content": "$1", @@ -3920,10 +3920,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "Ваша бесплатна пробна се завршава сутра." }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$, Ваша проба са завршава данас.", "placeholders": { "organization": { "content": "$1", @@ -3932,10 +3932,10 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "Ваша бесплатна пробна се завршава данас." }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "Кликните овде да додате начин плаћања." }, "joinOrganization": { "message": "Придружи Организацију" @@ -4492,7 +4492,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Бићете обавештени када захтев буде одобрен" }, "free": { "message": "Бесплатно", @@ -6529,7 +6529,7 @@ "message": "Генериши име" }, "generateEmail": { - "message": "Generate email" + "message": "Генеришите имејл" }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", @@ -9018,7 +9018,7 @@ "message": "Употребите Bitwarden Secrets Manager SDK на следећим програмским језицима да направите сопствене апликације." }, "ssoDescStart": { - "message": "Configure", + "message": "Подеси", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "ssoDescEnd": { @@ -9032,7 +9032,7 @@ "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configure ", + "message": "Подеси ", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Ажуриране пореске информације" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Непроверено" }, @@ -9885,22 +9897,22 @@ "message": "Descriptor code" }, "importantNotice": { - "message": "Important notice" + "message": "Важно обавештење" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Поставити дво-степенску пријаву" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden ће послати кôд на имејл вашег налога за верификовање пријављивања са нових уређаја почевши од фебруара 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": "Можете да подесите пријаву у два корака као алтернативни начин да заштитите свој налог или да промените свој имејл у један који можете да приступите." }, "remindMeLater": { - "message": "Remind me later" + "message": "Подсети ме касније" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Да ли имате поуздан приступ својим имејлом, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -9909,19 +9921,19 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Не, ненам" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Да, могу поуздано да приступим овим имејлом" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Упалити дво-степенску пријаву" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Променити имејл налога" }, "removeMembers": { - "message": "Remove members" + "message": "Уклони чланове" }, "claimedDomains": { "message": "Claimed domains" @@ -9954,7 +9966,7 @@ "message": "Claimed" }, "domainStatusUnderVerification": { - "message": "Under verification" + "message": "Под провером" }, "claimedDomainsDesc": { "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 6dd5fed6736..b2cd3a877d4 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index b4d2fb7aa8d..14d7bc4572c 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index c5a77826c9e..36ab0050700 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 5d2da786d83..c731b9ff87e 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index fb821407a16..ba53ebe7dd4 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -3,22 +3,22 @@ "message": "Tüm uygulamalar" }, "criticalApplications": { - "message": "Critical applications" + "message": "Kritik uygulamalar" }, "accessIntelligence": { "message": "Access Intelligence" }, "riskInsights": { - "message": "Risk Insights" + "message": "Risk İçgörüleri" }, "passwordRisk": { - "message": "Password Risk" + "message": "Parola Riski" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." + "message": "Uygulamalar genelinde risk altındaki parolaları (zayıf, açık veya yeniden kullanılan) gözden geçirin. Kullanıcılarınız için risk altındaki parolalara yönelik güvenlik eylemlerine öncelik vermek üzere en kritik uygulamalarınızı seçin." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Veri son güncellenme tarihi: $DATE$", "placeholders": { "date": { "content": "$1", @@ -30,10 +30,10 @@ "message": "Bildirilen üyeler" }, "revokeMembers": { - "message": "Revoke members" + "message": "Üyeleri iptal et" }, "restoreMembers": { - "message": "Restore members" + "message": "Üyeleri geri yükle" }, "cannotRestoreAccessError": { "message": "Cannot restore organization access" @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Doğrulanmadı" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 89d538e1a2a..c17befc27ce 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Податкову інформацію оновлено" }, + "billingInvalidTaxIdError": { + "message": "Недійсний ІПН. Якщо ви вважаєте це помилкою, зверніться до служби підтримки." + }, + "billingTaxIdTypeInferenceError": { + "message": "Не вдалося перевірити ваш ІПН. Якщо ви вважаєте це помилкою, зверніться до служби підтримки." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Недійсний ІПН. Якщо ви вважаєте це помилкою, зверніться до служби підтримки." + }, + "billingPreviewInvoiceError": { + "message": "Під час перегляду рахунку виникла помилка. Повторіть спробу пізніше." + }, "unverified": { "message": "Не перевірений" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Назва організації не може перевищувати 50 символів." + }, + "resellerRenewalWarning": { + "message": "Ваша передплата невдовзі поновиться. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "Рахунок за вашу передплату випущено $ISSUED_DATE$. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "Рахунок за вашу передплату ще не сплачено. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index c8300de6fbf..9f4156014c6 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "Updated tax information" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "Unverified" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index e0856dc4350..a7d73801372 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1521,7 +1521,7 @@ "message": "确认机密导出" }, "exportWarningDesc": { - "message": "本次导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。用完后请立即将其删除。" + "message": "此导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。使用完后请立即将其删除。" }, "exportSecretsWarningDesc": { "message": "本次导出包含未加密格式的机密数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。用完后请立即将其删除。" @@ -1716,7 +1716,7 @@ "message": "请重新登录。" }, "logBackInOthersToo": { - "message": "请重新登录。如果您还在使用其他 Bitwarden 应用,也请注销并重新登陆。" + "message": "请重新登录。如果您还在使用其他 Bitwarden 应用程序,也请注销并重新登陆。" }, "changeMasterPassword": { "message": "修改主密码" @@ -2018,7 +2018,7 @@ "message": "添加自定义域名" }, "newCustomDomainDesc": { - "message": "输入用逗号分隔的域名列表。只能输入「基础」域名,不要输入子域名。例如,输入「google.com」而不是「www.google.com」。您也可以输入「androidapp://package.name」以将 Android 应用程序与其他网站域名关联。" + "message": "输入用逗号分隔的域名列表。只能输入「基础」域名,不要输入子域名。例如,输入「google.com」而不是「www.google.com」。您也可以输入「androidapp://package.name」以将 Android App 与其他网站域名关联。" }, "customDomainX": { "message": "自定义域名 $INDEX$", @@ -2135,7 +2135,7 @@ } }, "continueToExternalUrlDesc": { - "message": "您将离开 Bitwarden 并将在新窗口中启动一个外部网站。" + "message": "您将离开 Bitwarden 并将在新窗口中打开一个外部网站。" }, "twoStepContinueToBitwardenUrlTitle": { "message": "前往 bitwarden.com 吗?" @@ -2180,13 +2180,13 @@ "message": "保存表单。" }, "twoFactorYubikeyWarning": { - "message": "由于平台的限制,YubiKey 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 YubiKey 时可以访问您的账户。支持的平台:" + "message": "由于平台限制,YubiKey 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 YubiKey 时可以访问您的账户。支持的平台:" }, "twoFactorYubikeySupportUsb": { "message": "具有可使用 YubiKey 的 USB 端口的设备上的网页版密码库、桌面应用程序、CLI 以及浏览器扩展。" }, "twoFactorYubikeySupportMobile": { - "message": "具有 NFC 功能或可使用 YubiKey 的数据端口的设备上的移动应用程序。" + "message": "具有 NFC 功能或可使用 YubiKey 的数据端口的设备上的移动 App。" }, "yubikeyX": { "message": "YubiKey $INDEX$", @@ -2231,7 +2231,7 @@ "message": "禁用全部钥匙" }, "twoFactorDuoDesc": { - "message": "输入 Duo 管理面板提供的 Bitwarden 应用信息。" + "message": "输入 Duo 管理面板提供的 Bitwarden 应用程序信息。" }, "twoFactorDuoClientId": { "message": "Client ID" @@ -2282,7 +2282,7 @@ "message": "保存表单。" }, "twoFactorU2fWarning": { - "message": "由于平台的限制,FIDO U2F 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 FIDO U2F 时可以访问您的账户。支持的平台:" + "message": "由于平台限制,FIDO U2F 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在无法使用 FIDO U2F 时可以访问您的账户。支持的平台:" }, "twoFactorU2fSupportWeb": { "message": "桌面/笔记本电脑上支持 U2F 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页版密码库和浏览器扩展。" @@ -2297,7 +2297,7 @@ "message": "读取安全钥匙时出现问题,请重试。" }, "twoFactorWebAuthnWarning": { - "message": "由于平台限制,无法在所有 Bitwarden 应用程序中使用 WebAuthn。您应该启用另一个两步登录提供程序,以便在 WebAuthn 无法使用时可以访问您的账户。支持的平台有:" + "message": "由于平台限制,WebAuthn 不能在所有 Bitwarden 应用程序上使用。您应该启用另一个两步登录提供程序,以便在 WebAuthn 无法使用时可以访问您的账户。支持的平台有:" }, "twoFactorWebAuthnSupportWeb": { "message": "桌面/笔记本电脑上支持 WebAuthn 的浏览器(启用了 FIDO U2F 的 Chrome、Opera、Vivaldi 或 Firefox)中的网页密码库和浏览器扩展。" @@ -4286,7 +4286,7 @@ "message": "为了提高安全性,我们更改了加密方案。请在下方输入您的主密码以立即更新您的加密密钥。" }, "updateEncryptionKeyWarning": { - "message": "更新加密密钥后,您需要注销所有正在使用的 Bitwarden 应用(比如移动 App 或者浏览器扩展)后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但是,可能会有所延迟。" + "message": "更新加密密钥后,您需要注销所有正在使用的 Bitwarden 应用程序(比如移动 App 或者浏览器扩展)后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但是,可能会有所延迟。" }, "updateEncryptionKeyExportWarning": { "message": "您保存的任何已加密导出也将变为无效。" @@ -4707,7 +4707,7 @@ "message": "包括 VAT/GST 信息(可选)" }, "taxIdNumber": { - "message": "VAT/GST 税号" + "message": "VAT/GST 税务 ID" }, "taxInfoUpdated": { "message": "税务信息已更新。" @@ -8061,7 +8061,7 @@ } }, "masterPasswordMinimumlength": { - "message": "主密码长度最少为 $LENGTH$ 个字符。", + "message": "主密码长度必须至少为 $LENGTH$ 个字符。", "placeholders": { "length": { "content": "$1", @@ -9058,7 +9058,7 @@ "message": "使用适合您平台的实施指南为 Bitwarden 配置设备管理。" }, "integrationCardTooltip": { - "message": "启动 $INTEGRATION$ 实施指南。", + "message": "打开 $INTEGRATION$ 实施指南。", "placeholders": { "integration": { "content": "$1", @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "更新了税务信息" }, + "billingInvalidTaxIdError": { + "message": "无效的税务 ID,如有疑问,请联系支持。" + }, + "billingTaxIdTypeInferenceError": { + "message": "我们无法验证您的税务 ID,如有疑问,请联系支持。" + }, + "billingPreviewInvalidTaxIdError": { + "message": "无效的税务 ID,如有疑问,请联系支持。" + }, + "billingPreviewInvoiceError": { + "message": "预览账单时出错。请稍后再试。" + }, "unverified": { "message": "未验证" }, @@ -9891,7 +9903,7 @@ "message": "设置两步登录" }, "newDeviceVerificationNoticeContentPage1": { - "message": "从 2025 年 02 月开始,Bitwarden 将向您的账户电子邮箱发送一个代码,以验证来自新设备的登录。" + "message": "从 2025 年 02 月起,当有来自新设备的登录时,Bitwarden 将向您的账户电子邮箱发送验证码。" }, "newDeviceVerificationNoticeContentPage2": { "message": "您可以设置两步登录作为保护账户的替代方法,或将您的电子邮箱更改为您可以访问的电子邮箱。" @@ -9900,7 +9912,7 @@ "message": "稍后提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "您能可靠地访问您的电子邮箱 $EMAIL$ 吗?", + "message": "您能正常访问您的电子邮箱 $EMAIL$ 吗?", "placeholders": { "email": { "content": "$1", @@ -9912,7 +9924,7 @@ "message": "不,我不能" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "是的,我可以可靠地访问我的电子邮箱" + "message": "是的,我可以正常访问我的电子邮箱" }, "turnOnTwoStepLogin": { "message": "开启两步登录" @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "组织名称不能超过 50 个字符。" + }, + "resellerRenewalWarning": { + "message": "您的订阅即将续订。为确保服务不中断,请在 $RENEWAL_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "您的订阅账单已于 $ISSUED_DATE$ 开具。为确保服务不中断,请在 $DUE_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "您的订阅账单尚未支付。为确保服务不中断,请在 $GRACE_PERIOD_END$ 之前联系 $RESELLER$ 确认您的续订。", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index bf15ea2a20a..ae60ea3cdb7 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -9271,6 +9271,18 @@ "updatedTaxInformation": { "message": "已更新稅務資訊" }, + "billingInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingTaxIdTypeInferenceError": { + "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvalidTaxIdError": { + "message": "Invalid tax ID, if you believe this is an error please contact support." + }, + "billingPreviewInvoiceError": { + "message": "An error occurred while previewing the invoice. Please try again later." + }, "unverified": { "message": "未驗證" }, @@ -10007,5 +10019,48 @@ }, "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." + }, + "resellerRenewalWarning": { + "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "renewal_date": { + "content": "$2", + "example": "01/01/2024" + } + } + }, + "resellerOpenInvoiceWarning": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "issued_date": { + "content": "$2", + "example": "01/01/2024" + }, + "due_date": { + "content": "$3", + "example": "01/15/2024" + } + } + }, + "resellerPastDueWarning": { + "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "placeholders": { + "reseller": { + "content": "$1", + "example": "Reseller Name" + }, + "grace_period_end": { + "content": "$2", + "example": "02/14/2024" + } + } } } From 534e42b9f0692b55d026dda71908ee27e4a5343f Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:31:16 +0100 Subject: [PATCH 059/270] [PM-16432] Remove v1 account security settings (#12578) * Remove v1 account security settings Delete v1 component Remove conditional routing based on extension refresh feature flag * Remove unused import --------- Co-authored-by: Daniel James Smith --- .../account-security-v1.component.html | 140 ----- .../settings/account-security-v1.component.ts | 499 ------------------ .../settings/account-security.component.ts | 2 - apps/browser/src/popup/app-routing.module.ts | 6 +- apps/browser/src/popup/app.module.ts | 2 - 5 files changed, 3 insertions(+), 646 deletions(-) delete mode 100644 apps/browser/src/auth/popup/settings/account-security-v1.component.html delete mode 100644 apps/browser/src/auth/popup/settings/account-security-v1.component.ts diff --git a/apps/browser/src/auth/popup/settings/account-security-v1.component.html b/apps/browser/src/auth/popup/settings/account-security-v1.component.html deleted file mode 100644 index dff9675743f..00000000000 --- a/apps/browser/src/auth/popup/settings/account-security-v1.component.html +++ /dev/null @@ -1,140 +0,0 @@ - -
    - -
    -

    - {{ "accountSecurity" | i18n }} -

    -
    - -
    -
    -
    -
    -

    {{ "unlockMethods" | i18n }}

    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -

    {{ "sessionTimeoutHeader" | i18n }}

    -
    - - - {{ - "vaultTimeoutPolicyWithActionInEffect" - | i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n) - }} - - - {{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }} - - - {{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }} - - - - -
    - - -
    - -
    -
    -
    -

    {{ "otherOptions" | i18n }}

    -
    - - - - - -
    -
    -
    diff --git a/apps/browser/src/auth/popup/settings/account-security-v1.component.ts b/apps/browser/src/auth/popup/settings/account-security-v1.component.ts deleted file mode 100644 index d06724bf657..00000000000 --- a/apps/browser/src/auth/popup/settings/account-security-v1.component.ts +++ /dev/null @@ -1,499 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { - BehaviorSubject, - combineLatest, - concatMap, - distinctUntilChanged, - filter, - firstValueFrom, - map, - Observable, - pairwise, - startWith, - Subject, - switchMap, - takeUntil, -} from "rxjs"; - -import { FingerprintDialogComponent } from "@bitwarden/auth/angular"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { - VaultTimeout, - VaultTimeoutOption, - VaultTimeoutStringType, -} from "@bitwarden/common/types/vault-timeout.type"; -import { DialogService } from "@bitwarden/components"; -import { KeyService, BiometricStateService, BiometricsService } from "@bitwarden/key-management"; - -import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; -import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { SetPinComponent } from "../components/set-pin.component"; - -import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; - -@Component({ - selector: "auth-account-security", - templateUrl: "account-security-v1.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class AccountSecurityComponent implements OnInit, OnDestroy { - protected readonly VaultTimeoutAction = VaultTimeoutAction; - - availableVaultTimeoutActions: VaultTimeoutAction[] = []; - vaultTimeoutOptions: VaultTimeoutOption[]; - vaultTimeoutPolicyCallout: Observable<{ - timeout: { hours: string; minutes: string }; - action: VaultTimeoutAction; - }>; - supportsBiometric: boolean; - showChangeMasterPass = true; - accountSwitcherEnabled = false; - - form = this.formBuilder.group({ - vaultTimeout: [null as VaultTimeout | null], - vaultTimeoutAction: [VaultTimeoutAction.Lock], - pin: [null as boolean | null], - biometric: false, - enableAutoBiometricsPrompt: true, - }); - - private refreshTimeoutSettings$ = new BehaviorSubject(undefined); - private destroy$ = new Subject(); - - constructor( - private accountService: AccountService, - private pinService: PinServiceAbstraction, - private policyService: PolicyService, - private formBuilder: FormBuilder, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - private vaultTimeoutService: VaultTimeoutService, - private vaultTimeoutSettingsService: VaultTimeoutSettingsService, - public messagingService: MessagingService, - private environmentService: EnvironmentService, - private keyService: KeyService, - private stateService: StateService, - private userVerificationService: UserVerificationService, - private dialogService: DialogService, - private changeDetectorRef: ChangeDetectorRef, - private biometricStateService: BiometricStateService, - private biometricsService: BiometricsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - const maximumVaultTimeoutPolicy = this.policyService.get$(PolicyType.MaximumVaultTimeout); - this.vaultTimeoutPolicyCallout = maximumVaultTimeoutPolicy.pipe( - filter((policy) => policy != null), - map((policy) => { - let timeout; - if (policy.data?.minutes) { - timeout = { - hours: Math.floor(policy.data?.minutes / 60).toString(), - minutes: (policy.data?.minutes % 60).toString(), - }; - } - return { timeout: timeout, action: policy.data?.action }; - }), - ); - - const showOnLocked = - !this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari(); - - this.vaultTimeoutOptions = [ - { name: this.i18nService.t("immediately"), value: 0 }, - { name: this.i18nService.t("oneMinute"), value: 1 }, - { name: this.i18nService.t("fiveMinutes"), value: 5 }, - { name: this.i18nService.t("fifteenMinutes"), value: 15 }, - { name: this.i18nService.t("thirtyMinutes"), value: 30 }, - { name: this.i18nService.t("oneHour"), value: 60 }, - { name: this.i18nService.t("fourHours"), value: 240 }, - ]; - - if (showOnLocked) { - this.vaultTimeoutOptions.push({ - name: this.i18nService.t("onLocked"), - value: VaultTimeoutStringType.OnLocked, - }); - } - - this.vaultTimeoutOptions.push({ - name: this.i18nService.t("onRestart"), - value: VaultTimeoutStringType.OnRestart, - }); - this.vaultTimeoutOptions.push({ - name: this.i18nService.t("never"), - value: VaultTimeoutStringType.Never, - }); - - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - - let timeout = await firstValueFrom( - this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(activeAccount.id), - ); - if (timeout === VaultTimeoutStringType.OnLocked && !showOnLocked) { - timeout = VaultTimeoutStringType.OnRestart; - } - - const initialValues = { - vaultTimeout: timeout, - vaultTimeoutAction: await firstValueFrom( - this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), - ), - pin: await this.pinService.isPinSet(activeAccount.id), - biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(), - enableAutoBiometricsPrompt: await firstValueFrom( - this.biometricStateService.promptAutomatically$, - ), - }; - this.form.patchValue(initialValues, { emitEvent: false }); - - this.supportsBiometric = await this.biometricsService.supportsBiometric(); - this.showChangeMasterPass = await this.userVerificationService.hasMasterPassword(); - - this.form.controls.vaultTimeout.valueChanges - .pipe( - startWith(initialValues.vaultTimeout), // emit to init pairwise - pairwise(), - concatMap(async ([previousValue, newValue]) => { - await this.saveVaultTimeout(previousValue, newValue); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.form.controls.vaultTimeoutAction.valueChanges - .pipe( - startWith(initialValues.vaultTimeoutAction), // emit to init pairwise - pairwise(), - concatMap(async ([previousValue, newValue]) => { - await this.saveVaultTimeoutAction(previousValue, newValue); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.form.controls.pin.valueChanges - .pipe( - concatMap(async (value) => { - await this.updatePin(value); - this.refreshTimeoutSettings$.next(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.form.controls.biometric.valueChanges - .pipe( - distinctUntilChanged(), - concatMap(async (enabled) => { - await this.updateBiometric(enabled); - if (enabled) { - this.form.controls.enableAutoBiometricsPrompt.enable(); - } else { - this.form.controls.enableAutoBiometricsPrompt.disable(); - } - this.refreshTimeoutSettings$.next(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.refreshTimeoutSettings$ - .pipe( - switchMap(() => - combineLatest([ - this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(), - this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), - ]), - ), - takeUntil(this.destroy$), - ) - .subscribe(([availableActions, action]) => { - this.availableVaultTimeoutActions = availableActions; - this.form.controls.vaultTimeoutAction.setValue(action, { emitEvent: false }); - // NOTE: The UI doesn't properly update without detect changes. - // I've even tried using an async pipe, but it still doesn't work. I'm not sure why. - // Using an async pipe means that we can't call `detectChanges` AFTER the data has change - // meaning that we are forced to use regular class variables instead of observables. - this.changeDetectorRef.detectChanges(); - }); - - this.refreshTimeoutSettings$ - .pipe( - switchMap(() => - combineLatest([ - this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(), - maximumVaultTimeoutPolicy, - ]), - ), - takeUntil(this.destroy$), - ) - .subscribe(([availableActions, policy]) => { - if (policy?.data?.action || availableActions.length <= 1) { - this.form.controls.vaultTimeoutAction.disable({ emitEvent: false }); - } else { - this.form.controls.vaultTimeoutAction.enable({ emitEvent: false }); - } - }); - } - - async saveVaultTimeout(previousValue: VaultTimeout, newValue: VaultTimeout) { - if (newValue === VaultTimeoutStringType.Never) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "warning" }, - content: { key: "neverLockWarning" }, - type: "warning", - }); - - if (!confirmed) { - this.form.controls.vaultTimeout.setValue(previousValue, { emitEvent: false }); - return; - } - } - - // The minTimeoutError does not apply to browser because it supports Immediately - // So only check for the policyError - if (this.form.controls.vaultTimeout.hasError("policyError")) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("vaultTimeoutTooLarge"), - ); - return; - } - - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - - const vaultTimeoutAction = await firstValueFrom( - this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), - ); - - await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( - activeAccount.id, - newValue, - vaultTimeoutAction, - ); - if (newValue === VaultTimeoutStringType.Never) { - this.messagingService.send("bgReseedStorage"); - } - } - - async saveVaultTimeoutAction(previousValue: VaultTimeoutAction, newValue: VaultTimeoutAction) { - if (newValue === VaultTimeoutAction.LogOut) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "vaultTimeoutLogOutConfirmationTitle" }, - content: { key: "vaultTimeoutLogOutConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - this.form.controls.vaultTimeoutAction.setValue(previousValue, { - emitEvent: false, - }); - return; - } - } - - if (this.form.controls.vaultTimeout.hasError("policyError")) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("vaultTimeoutTooLarge"), - ); - return; - } - - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - - await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( - activeAccount.id, - this.form.value.vaultTimeout, - newValue, - ); - this.refreshTimeoutSettings$.next(); - } - - async updatePin(value: boolean) { - if (value) { - const dialogRef = SetPinComponent.open(this.dialogService); - - if (dialogRef == null) { - this.form.controls.pin.setValue(false, { emitEvent: false }); - return; - } - - const userHasPinSet = await firstValueFrom(dialogRef.closed); - this.form.controls.pin.setValue(userHasPinSet, { emitEvent: false }); - } else { - await this.vaultTimeoutSettingsService.clear(); - } - } - - async updateBiometric(enabled: boolean) { - if (enabled && this.supportsBiometric) { - let granted; - try { - granted = await BrowserApi.requestPermission({ permissions: ["nativeMessaging"] }); - } catch (e) { - // eslint-disable-next-line - console.error(e); - - if (this.platformUtilsService.isFirefox() && BrowserPopupUtils.inSidebar(window)) { - await this.dialogService.openSimpleDialog({ - title: { key: "nativeMessaginPermissionSidebarTitle" }, - content: { key: "nativeMessaginPermissionSidebarDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "info", - }); - - this.form.controls.biometric.setValue(false); - return; - } - } - - if (!granted) { - await this.dialogService.openSimpleDialog({ - title: { key: "nativeMessaginPermissionErrorTitle" }, - content: { key: "nativeMessaginPermissionErrorDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - - this.form.controls.biometric.setValue(false); - return; - } - - const awaitDesktopDialogRef = AwaitDesktopDialogComponent.open(this.dialogService); - const awaitDesktopDialogClosed = firstValueFrom(awaitDesktopDialogRef.closed); - - await this.keyService.refreshAdditionalKeys(); - - await Promise.race([ - awaitDesktopDialogClosed.then(async (result) => { - if (result !== true) { - this.form.controls.biometric.setValue(false); - } - }), - this.biometricsService - .authenticateBiometric() - .then((result) => { - this.form.controls.biometric.setValue(result); - if (!result) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorEnableBiometricTitle"), - this.i18nService.t("errorEnableBiometricDesc"), - ); - } - }) - .catch((e) => { - // Handle connection errors - this.form.controls.biometric.setValue(false); - - const error = BiometricErrors[e.message as BiometricErrorTypes]; - - // 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.dialogService.openSimpleDialog({ - title: { key: error.title }, - content: { key: error.description }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "danger", - }); - }) - .finally(() => { - awaitDesktopDialogRef.close(true); - }), - ]); - } else { - await this.biometricStateService.setBiometricUnlockEnabled(false); - await this.biometricStateService.setFingerprintValidated(false); - } - } - - async updateAutoBiometricsPrompt() { - await this.biometricStateService.setPromptAutomatically( - this.form.value.enableAutoBiometricsPrompt, - ); - } - - async changePassword() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToWebApp" }, - content: { key: "changeMasterPasswordOnWebConfirmation" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const env = await firstValueFrom(this.environmentService.environment$); - await BrowserApi.createNewTab(env.getWebVaultUrl()); - } - } - - async twoStep() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "twoStepLogin" }, - content: { key: "twoStepLoginConfirmation" }, - type: "info", - }); - 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 - BrowserApi.createNewTab("https://bitwarden.com/help/setup-two-step-login/"); - } - } - - async fingerprint() { - const fingerprint = await this.keyService.getFingerprint(await this.stateService.getUserId()); - - const dialogRef = FingerprintDialogComponent.open(this.dialogService, { - fingerprint, - }); - - return firstValueFrom(dialogRef.closed); - } - - async lock() { - await this.vaultTimeoutService.lock(); - } - - async logOut() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "logOut" }, - content: { key: "logOutConfirmation" }, - type: "info", - }); - - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - if (confirmed) { - this.messagingService.send("logout", { userId: userId }); - } - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } -} diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index cf923ac74b5..86eea889fdd 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -60,7 +60,6 @@ import { BrowserApi } from "../../../platform/browser/browser-api"; import { enableAccountSwitching } from "../../../platform/flags"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; import { SetPinComponent } from "../components/set-pin.component"; @@ -82,7 +81,6 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; JslibModule, LinkModule, PopOutComponent, - PopupFooterComponent, PopupHeaderComponent, PopupPageComponent, RouterModule, diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index b7bc5643ac4..49d680cd752 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -65,7 +65,6 @@ import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-req import { RegisterComponent } from "../auth/popup/register.component"; import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; -import { AccountSecurityComponent as AccountSecurityV1Component } from "../auth/popup/settings/account-security-v1.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { SsoComponentV1 } from "../auth/popup/sso-v1.component"; import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component"; @@ -351,11 +350,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }), - ...extensionRefreshSwap(AccountSecurityV1Component, AccountSecurityComponent, { + { path: "account-security", + component: AccountSecurityComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, ...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, { path: "notifications", canActivate: [authGuard], diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 04d681812fe..83475a661c9 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -29,7 +29,6 @@ import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-req import { RegisterComponent } from "../auth/popup/register.component"; import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; -import { AccountSecurityComponent as AccountSecurityComponentV1 } from "../auth/popup/settings/account-security-v1.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"; @@ -165,7 +164,6 @@ import "../platform/popup/locales"; TwoFactorOptionsComponent, UpdateTempPasswordComponent, UserVerificationComponent, - AccountSecurityComponentV1, VaultTimeoutInputComponent, ViewComponent, ViewCustomFieldsComponent, From 9f670c68208b24a886add9ea97f14aca3dd35e67 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:34:03 +0000 Subject: [PATCH 060/270] Autosync the updated translations (#12673) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/ar/messages.json | 6 +- apps/desktop/src/locales/fi/messages.json | 4 +- apps/desktop/src/locales/fr/messages.json | 24 +-- apps/desktop/src/locales/it/messages.json | 140 +++++++++--------- apps/desktop/src/locales/ja/messages.json | 148 +++++++++---------- apps/desktop/src/locales/nl/messages.json | 14 +- apps/desktop/src/locales/sk/messages.json | 2 +- apps/desktop/src/locales/sr/messages.json | 52 +++---- apps/desktop/src/locales/tr/messages.json | 4 +- apps/desktop/src/locales/zh_CN/messages.json | 26 ++-- 10 files changed, 210 insertions(+), 210 deletions(-) diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index d9c702b19d8..a15dbb5f078 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -3399,10 +3399,10 @@ "message": "ملاحظة هامة" }, "setupTwoStepLogin": { - "message": "إعداد المصادقة الثنائية" + "message": "إعداد تسجيل الدخول بخطوتين" }, "newDeviceVerificationNoticeContentPage1": { - "message": "سيقوم Bitwarden بإرسال رمز إلى البريد الإلكتروني الخاص بحسابك للتحقق من تسجيلات الدخول من الأجهزة الجديدة ابتداء من فبراير 2025." + "message": "سيقوم Bitwarden بإرسال رمز إلى البريد الإلكتروني الخاص بحسابك للتحقق من تسجيلات الدخول من الأجهزة الجديدة ابتداءً من فبراير 2025." }, "newDeviceVerificationNoticeContentPage2": { "message": "يمكنك إعداد المصادقة الثنائية كطريقة بديلة لحماية حسابك أو تغيير بريدك الإلكتروني إلى بريد يمكنك الوصول إليه." @@ -3426,7 +3426,7 @@ "message": "نعم، يمكنني الوصول بشكل موثوق إلى بريدي الإلكتروني" }, "turnOnTwoStepLogin": { - "message": "تفعيل المصادقة الثنائية" + "message": "تشغيل تسجيل الدخول بخطوتين" }, "changeAcctEmail": { "message": "تغيير البريد الإلكتروني الخاص بالحساب" diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 226c44bc352..69092054f28 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -2729,7 +2729,7 @@ "message": "Laitteeseesi lähetettiin ilmoitus" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "Varmista, että vahvistavan laitteen holvi on avattu ja että se näyttää saman tunnistelausekkeen" }, "needAnotherOptionV1": { "message": "Tarvitsetko toisen vaihtoehdon?" @@ -3402,7 +3402,7 @@ "message": "Määritä kaksivaiheinen kirjautuminen" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden lähettää tilisi sähköpostiosoitteeseen koodin, jolla voit vahvistaa kirjautumiset uusista laitteista helmikuusta 2025 alkaen." }, "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." diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index d387cbb20a0..a353653e1e3 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -323,7 +323,7 @@ "message": "Générer un mot de passe" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Générer une phrase de passe" }, "type": { "message": "Type" @@ -467,7 +467,7 @@ "message": "Copier la clé privée SSH" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copier la phrase de passe", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -926,7 +926,7 @@ "message": "La session d'authentification a expiré. Veuillez redémarrer le processus de connexion." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL du serveur auto-hébergé", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1951,7 +1951,7 @@ "message": "Votre nouveau mot de passe principal ne répond pas aux exigences de politique de sécurité." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Obtenez des conseils, des annonces et des opportunités de recherche de la part de Bitwarden dans votre boîte de réception." }, "unsubscribe": { "message": "Se désabonner" @@ -2478,10 +2478,10 @@ "message": "Générer le nom d'utilisateur" }, "generateEmail": { - "message": "Generate email" + "message": "Générer un courriel" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "La valeur doit être comprise entre $MIN$ et $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2495,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Utilisez $RECOMMENDED$ caractères ou plus pour générer un mot de passe fort.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2505,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Utilisez $RECOMMENDED$ mots ou plus pour générer une phrase de passe forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2729,10 +2729,10 @@ "message": "A notification was sent to your device" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "Assurez-vous que votre compte est déverrouillé et que la phrase d'empreinte digitale correspond à celle de l'autre appareil" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Besoin d'une autre option ?" }, "fingerprintMatchInfo": { "message": "Veuillez vous assurer que votre coffre est déverrouillé et que la phrase d'empreinte correspond à celle de l'autre appareil." @@ -2741,13 +2741,13 @@ "message": "Phrase d'empreinte" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Vous serez notifié une fois que la demande sera approuvée" }, "needAnotherOption": { "message": "La connexion avec l'appareil doit être configurée dans les paramètres de l'application Bitwarden. Besoin d'une autre option ?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Afficher toutes les options de connexion" }, "viewAllLoginOptions": { "message": "Afficher toutes les options de connexion" diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index e8c0a3d1a39..e035a13606d 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -27,7 +27,7 @@ "message": "Nota sicura" }, "typeSshKey": { - "message": "SSH key" + "message": "Chiave SSH" }, "folders": { "message": "Cartelle" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Bentornato" }, "moveToOrgDesc": { "message": "Scegli un'organizzazione in cui vuoi spostare questo elemento. Spostarlo in un'organizzazione trasferisce la proprietà dell'elemento all'organizzazione. Una volta spostato, non sarai più il proprietario diretto di questo elemento." @@ -181,61 +181,61 @@ "message": "Indirizzo" }, "sshPrivateKey": { - "message": "Private key" + "message": "Chiave privata" }, "sshPublicKey": { - "message": "Public key" + "message": "Chiave pubblica" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Impronta digitale" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipo di chiave" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA a 2048 bit" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA a 3072 bit" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA a 4096 bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "È stata generata una nuova chiave SSH" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "La password inserita non è corretta." }, "importSshKey": { - "message": "Import" + "message": "Importa" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Conferma password" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Inserisci la password per la chiave SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Inserisci password" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "Sbloccare la cassaforte per approvare la richiesta di chiave SSH." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "Richiesta chiave SSH scaduta." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "Abilita agente SSH" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Abilita l'agente SSH per firmare le richieste SSH direttamente dalla tua cassaforte Bitwarden." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "L'agente SSH è un servizio rivolto agli sviluppatori che consente di firmare le richieste SSH direttamente dalla tua cassaforte Bitwarden." }, "premiumRequired": { "message": "Premium necessario" @@ -461,10 +461,10 @@ "message": "Copia password" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "Rigenera la chiave SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "Copia chiave privata SSH" }, "copyPassphrase": { "message": "Copia passphrase", @@ -624,7 +624,7 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nuovo in Bitwarden?" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -636,16 +636,16 @@ "message": "Accedi" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Accedi a Bitwarden" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Accedi con passkey" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Accedi con dispositivo" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usa il Single Sign-On" }, "submit": { "message": "Invia" @@ -920,10 +920,10 @@ "message": "URL del server" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Timeout autenticazione" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "La sessione di autenticazione è scaduta. Accedi di nuovo." }, "selfHostBaseUrl": { "message": "URL server autogestito", @@ -1393,13 +1393,13 @@ "message": "Cronologia delle password" }, "generatorHistory": { - "message": "Generator history" + "message": "Cronologia generatore" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Cancella cronologia generatore" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Se continui, tutte le voci verranno eliminate definitivamente dalla cronologia del generatore. Vuoi continuare?" }, "clear": { "message": "Cancella", @@ -1409,13 +1409,13 @@ "message": "Non ci sono password da mostrare." }, "clearHistory": { - "message": "Clear history" + "message": "Cancella cronologia" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Niente da mostrare" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Non hai generato niente di recente" }, "undo": { "message": "Annulla" @@ -1771,10 +1771,10 @@ "message": "L'eliminazione del tuo account è permanente. Non può essere annullata." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Impossibile eliminare account" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Questa azione non può essere completata perché il tuo account è di proprietà di un'organizzazione. Contatta l'amministratore della tua organizzazione per dettagli." }, "accountDeleted": { "message": "Account eliminato" @@ -2481,7 +2481,7 @@ "message": "Genera e-mail" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2495,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Usa $RECOMMENDED$ caratteri o più per generare una password forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2505,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Usa $RECOMMENDED$ parole o più per generare una passphrase forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2726,13 +2726,13 @@ "message": "Una notifica è stata inviata al tuo dispositivo." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Una notifica è stata inviata al tuo dispositivo" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "Assicurati che il tuo account sia sbloccato e che la frase dell'impronta digitale corrisponda nell'altro dispositivo" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Bisogno di un'altra opzione?" }, "fingerprintMatchInfo": { "message": "Assicurati che la tua cassaforte sia sbloccata e che la frase impronta corrisponda sull'altro dispositivo." @@ -2741,13 +2741,13 @@ "message": "Frase impronta" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Sarai notificato una volta che la richiesta sarà approvata" }, "needAnotherOption": { "message": "L'accesso con dispositivo deve essere abilitato nelle impostazioni dell'app Bitwarden. Ti serve un'altra opzione?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Visualizza tutte le opzioni di accesso" }, "viewAllLoginOptions": { "message": "Visualizza tutte le opzioni di accesso" @@ -2869,7 +2869,7 @@ "message": "Controlla se la tua password è presente in una violazione dei dati" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Accesso effettuato!" }, "important": { "message": "Importante:" @@ -2902,16 +2902,16 @@ "message": "Aggiornamento delle impostazioni consigliato" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "Ricorda questo dispositivo per rendere immediati i futuri accessi" }, "deviceApprovalRequired": { "message": "Approvazione del dispositivo obbligatoria. Seleziona un'opzione di approvazione:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Approvazione dispositivo richiesta" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Seleziona un'opzione di approvazione sotto" }, "rememberThisDevice": { "message": "Ricorda questo dispositivo" @@ -2966,7 +2966,7 @@ "message": "Email utente mancante" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Email utente attiva non trovata. Logout in corso." }, "deviceTrusted": { "message": "Dispositivo fidato" @@ -3363,55 +3363,55 @@ "message": "Non è stato possibile trovare nessuna porta libera per il login Sso." }, "authorize": { - "message": "Authorize" + "message": "Autorizza" }, "deny": { - "message": "Deny" + "message": "Nega" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "Conferma l'uso della chiave SSH" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "richiede l'accesso a" }, "unknownApplication": { - "message": "An application" + "message": "Un'applicazione" }, "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "L'importazione di chiavi SSH protette da password non è ancora supportata" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "La chiave SSH non è valida" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "Il tipo di chiave SSH non è supportato" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "Importa chiave dagli Appunti" }, "sshKeyPasted": { - "message": "SSH key imported successfully" + "message": "Chiave SSH importata correttamente" }, "fileSavedToDevice": { "message": "File salvato sul dispositivo. Gestisci dai download del dispositivo." }, "importantNotice": { - "message": "Important notice" + "message": "Notifica importante" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Imposta accesso in due passaggi" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden invierà un codice all'e-mail del tuo account per verificare gli accessi da nuovi dispositivi a partire da febbraio 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": "Puoi impostare l'accesso in due passaggi come modo alternativo per proteggere il tuo account, o cambiare la tua e-mail in una alla quale puoi accedere." }, "remindMeLater": { - "message": "Remind me later" + "message": "Ricordamelo più tardi" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Hai accesso affidabile alla tua e-mail, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3420,15 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No, non ce l'ho" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Sì, posso accedere in modo affidabile alla mia e-mail" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Attiva accesso in due passaggi" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Cambia l'e-mail dell'account" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index c146fb2404c..7315d74a70f 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -27,7 +27,7 @@ "message": "セキュアメモ" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH キー" }, "folders": { "message": "フォルダー" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "ようこそ" }, "moveToOrgDesc": { "message": "このアイテムを移動する組織を選択してください。組織に移動すると、アイテムの所有権がその組織に移行します。 このアイテムが移動された後、あなたはこのアイテムの直接の所有者にはなりません。" @@ -181,16 +181,16 @@ "message": "住所" }, "sshPrivateKey": { - "message": "Private key" + "message": "秘密鍵" }, "sshPublicKey": { - "message": "Public key" + "message": "公開鍵" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "フィンガープリント" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "キーの種類" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,37 +205,37 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "新しい SSH 鍵が生成されました" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "入力されたパスワードが間違っています。" }, "importSshKey": { - "message": "Import" + "message": "インポート" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "パスワードを確認" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "SSH キーのパスワードを入力します。" }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "パスワードを入力" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "SSH キーリクエストを承認するには、保管庫のロックを解除してください。" }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "SSH キーの要求がタイムアウトしました。" }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "SSH エージェントを有効にする" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Bitwarden 保管庫から直接 SSH 要求に署名するために SSH エージェントを有効にします。" }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "SSH エージェントとは、Bitwarden 保管庫から直接 SSH リクエストに署名できる、開発者を対象としたサービスです。" }, "premiumRequired": { "message": "プレミアム会員専用" @@ -323,7 +323,7 @@ "message": "パスワードの自動生成" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "パスフレーズを生成" }, "type": { "message": "タイプ" @@ -461,13 +461,13 @@ "message": "パスワードのコピー" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "SSH キーを再生成" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "SSH 秘密鍵をコピー" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "パスフレーズをコピー", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -624,7 +624,7 @@ "message": "アカウントの作成" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Bitwarden は初めてですか?" }, "setAStrongPassword": { "message": "強力なパスワードを設定する" @@ -636,16 +636,16 @@ "message": "ログイン" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden にログイン" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "パスキーでログイン" }, "loginWithDevice": { - "message": "Log in with device" + "message": "デバイスでログイン" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "シングルサインオンを使用する" }, "submit": { "message": "送信" @@ -920,13 +920,13 @@ "message": "サーバー URL" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "認証のタイムアウト" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "認証セッションの有効期限が切れました。ログイン操作を最初からやり直してください。" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自己ホスト型サーバーの URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1320,7 +1320,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "メールアドレスをコピー" }, "copySecurityCode": { "message": "セキュリティコードのコピー", @@ -1393,13 +1393,13 @@ "message": "パスワードの履歴" }, "generatorHistory": { - "message": "Generator history" + "message": "生成履歴" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "生成履歴を消去" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "続行すると、すべてのエントリは生成履歴から完全に削除されます。続行してもよろしいですか?" }, "clear": { "message": "消去する", @@ -1409,13 +1409,13 @@ "message": "表示するパスワードがありません" }, "clearHistory": { - "message": "Clear history" + "message": "履歴を消去" }, "nothingToShow": { - "message": "Nothing to show" + "message": "表示するものがありません" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "最近生成したものはありません" }, "undo": { "message": "元に戻す" @@ -1771,10 +1771,10 @@ "message": "アカウントを恒久的に削除します。元に戻すことはできません。" }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "アカウントを削除できません" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "このアカウントは組織が所有しているため、操作を完了できません。詳しくは組織の管理者へご確認ください。" }, "accountDeleted": { "message": "アカウントが削除されました" @@ -2478,10 +2478,10 @@ "message": "ユーザー名を生成" }, "generateEmail": { - "message": "Generate email" + "message": "メールアドレスを生成" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2495,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " 強力なパスワードを生成するには、 $RECOMMENDED$ 文字以上を使用してください。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2505,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " 強力なパスフレーズを生成するには、 $RECOMMENDED$ 単語以上を使用してください。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2558,11 +2558,11 @@ "message": "外部転送サービスを使用してメールエイリアスを生成します。" }, "forwarderDomainName": { - "message": "Email domain", + "message": "メールアドレスのドメイン", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "選択したサービスでサポートされているドメインを選択してください", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -2726,13 +2726,13 @@ "message": "デバイスに通知を送信しました。" }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "お使いのデバイスに通知が送信されました" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末と一致していることを確認してください" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "別の選択肢が必要ですか?" }, "fingerprintMatchInfo": { "message": "保管庫がロックされていることと、パスフレーズが他のデバイスと一致していることを確認してください。" @@ -2741,13 +2741,13 @@ "message": "パスフレーズ" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "リクエストが承認されると通知されます" }, "needAnotherOption": { "message": "Bitwarden アプリの設定でデバイスでログインする必要があります。別のオプションが必要ですか?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "すべてのログインオプションを表示" }, "viewAllLoginOptions": { "message": "すべてのログインオプションを表示" @@ -2869,7 +2869,7 @@ "message": "このパスワードの既知のデータ流出を確認" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "ログインしました!" }, "important": { "message": "重要" @@ -2902,16 +2902,16 @@ "message": "設定の更新を推奨" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "このデバイスを記憶して今後のログインをシームレスにする" }, "deviceApprovalRequired": { "message": "デバイスの承認が必要です。以下から承認オプションを選択してください:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "デバイスの承認が必要です" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "以下の承認オプションを選択してください" }, "rememberThisDevice": { "message": "このデバイスを記憶する" @@ -2966,7 +2966,7 @@ "message": "ユーザーのメールアドレスがありません" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "アクティブなユーザーメールアドレスが見つかりません。ログアウトします。" }, "deviceTrusted": { "message": "信頼されたデバイス" @@ -3363,55 +3363,55 @@ "message": "SSO ログインのための空きポートが見つかりませんでした。" }, "authorize": { - "message": "Authorize" + "message": "認可" }, "deny": { - "message": "Deny" + "message": "拒否" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "SSH 鍵の使用を確認します" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "がアクセスを要求しています: " }, "unknownApplication": { - "message": "An application" + "message": "アプリ" }, "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "パスワードで保護された SSH キーのインポートはまだサポートされていません" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "SSH キーが無効です" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "サポートされていない種類の SSH キーです" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "クリップボードからキーをインポート" }, "sshKeyPasted": { - "message": "SSH key imported successfully" + "message": "SSH キーのインポートに成功しました" }, "fileSavedToDevice": { "message": "ファイルをデバイスに保存しました。デバイスのダウンロードで管理できます。" }, "importantNotice": { - "message": "Important notice" + "message": "重要なお知らせ" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "2段階認証によるログインを設定する" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden は2025年2月以降、新しいデバイスからのログイン時にアカウントのメールアドレスに確認コードを送信します。" }, "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": "代わりに2段階認証によるログインでアカウントを保護するか、メールアドレスをあなたがアクセスできるものに変更できます。" }, "remindMeLater": { - "message": "Remind me later" + "message": "後で再通知" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "新しいメールアドレス $EMAIL$ はあなたが管理しているものですか?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3420,15 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "いいえ、違います" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "はい、メールアドレスには私が確実にアクセスできます" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "2段階認証によるログインを有効にする" }, "changeAcctEmail": { - "message": "Change account email" + "message": "アカウントのメールアドレスを変更する" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 2cd0232e5d4..a23ef820ab7 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -945,7 +945,7 @@ "message": "Pictogrammenserver-URL" }, "environmentSaved": { - "message": "De omgevings-URL's zijn opgeslagen." + "message": "Omgevings-URL's opgeslagen" }, "ok": { "message": "Ok" @@ -1002,7 +1002,7 @@ "message": "Nieuwe map toevoegen" }, "view": { - "message": "Beeld" + "message": "Weergeven" }, "account": { "message": "Account" @@ -1268,7 +1268,7 @@ "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "Controleren op updates" + "message": "Controleren op updates…" }, "version": { "message": "Versie $VERSION_NUM$", @@ -3026,7 +3026,7 @@ } }, "multipleInputEmails": { - "message": "Een of meer e-mailadressen zijn ongeldig" + "message": "Eén of meer e-mailadressen zijn ongeldig" }, "inputTrimValidator": { "message": "Invoer mag niet alleen witruimte bevatten.", @@ -3051,7 +3051,7 @@ "message": "-- Type om te filteren --" }, "multiSelectLoading": { - "message": "Opties ophalen..." + "message": "Opties ophalen…" }, "multiSelectNotFound": { "message": "Geen items gevonden" @@ -3243,7 +3243,7 @@ "message": "LastPass Email" }, "importingYourAccount": { - "message": "Account impoteren..." + "message": "Account impoteren…" }, "lastPassMFARequired": { "message": "LastPass multifactor-authenticatie vereist" @@ -3396,7 +3396,7 @@ "message": "Bestand op apparaat opgeslagen. Beheer vanaf de downloads op je apparaat." }, "importantNotice": { - "message": "Belangrijke mededeling" + "message": "Belangrijke melding" }, "setupTwoStepLogin": { "message": "Tweestapsaanmelding instellen" diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 6013b9b61e0..cec06c3c028 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -3399,7 +3399,7 @@ "message": "Dôležité upozornenie" }, "setupTwoStepLogin": { - "message": "Nastavenie dvojstupňového prihlásenia" + "message": "Nastaviť dvojstupňové prihlásenie" }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden vám od februára 2025 pošle na e-mail vášho účtu kód na overenie prihlásenia z nových zariadení." diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 3196f2c208b..9f9dec2dd68 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -208,19 +208,19 @@ "message": "Генерисан је нови SSH кључ" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Лозинка коју сте унели није тачна." }, "importSshKey": { - "message": "Import" + "message": "Увоз" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Потврда лозинке" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Унети лозинку за SSH кључ." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Унесите лозинку" }, "sshAgentUnlockRequired": { "message": "Откључајте свој сеф да бисте одобрили захтев за SSH кључ." @@ -920,10 +920,10 @@ "message": "УРЛ Сервера" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Истекло је време аутентификације" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Истекло је време сесије за аутентификацију. Молим вас покрените процес пријаве поново." }, "selfHostBaseUrl": { "message": "УРЛ сервера који се самостално хостује", @@ -1393,13 +1393,13 @@ "message": "Историја Лозинке" }, "generatorHistory": { - "message": "Generator history" + "message": "Генератор историје" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Испразнити генератор историје" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Ако наставите, сви уноси ће бити трајно избрисани из генератора историје. Да ли сте сигурни да желите да наставите?" }, "clear": { "message": "Очисти", @@ -1409,13 +1409,13 @@ "message": "Нема лозинки за приказивање." }, "clearHistory": { - "message": "Clear history" + "message": "Обриши историју" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Ништа за приказ" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Недавно нисте ништа генерисали" }, "undo": { "message": "Опозови" @@ -2481,7 +2481,7 @@ "message": "Генеришите имејл" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Вредност мора бити између $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2495,7 +2495,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Употребити $RECOMMENDED$ знакова или више да бисте генерисали јаку лозинку.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2505,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Употребити $RECOMMENDED$ речи или више да бисте генерисали јаку приступну фразу.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2726,13 +2726,13 @@ "message": "Обавештење је послато на ваш уређај." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Обавештење је послато на ваш уређај" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "Уверите се да је ваш налог откључан и да се фраза отиска подудара на другом уређају" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Треба Вам друга опција?" }, "fingerprintMatchInfo": { "message": "Уверите се да је ваш сеф откључан и да се фраза отиска прста подудара на другом уређају." @@ -2741,13 +2741,13 @@ "message": "Сигурносна фраза сефа" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Бићете обавештени када захтев буде одобрен" }, "needAnotherOption": { "message": "Пријава помоћу уређаја мора бити подешена у подешавањима Bitwarden апликације. Потребна је друга опција?" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Погледајте сав извештај у опције" }, "viewAllLoginOptions": { "message": "Погредајте све опције пријављивања" @@ -2869,7 +2869,7 @@ "message": "Проверите познате упада података за ову лозинку" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Пријављено!" }, "important": { "message": "Важно:" @@ -2902,16 +2902,16 @@ "message": "Препоручено ажурирање поставки" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "Запамтити овај уређај да би будуће пријаве биле беспрекорне" }, "deviceApprovalRequired": { "message": "Потребно је одобрење уређаја. Изаберите опцију одобрења испод:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Потребно је одобрење уређаја" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Изаберите опцију одобрења у наставку" }, "rememberThisDevice": { "message": "Запамти овај уређај" @@ -2966,7 +2966,7 @@ "message": "Недостаје имејл корисника" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Имејл активног корисника није пронађен. Одјављивање." }, "deviceTrusted": { "message": "Уређај поуздан" diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 9ff0efe6854..8357f8616bc 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -3363,10 +3363,10 @@ "message": "SSO girişi için açık port bulunamadı." }, "authorize": { - "message": "Authorize" + "message": "Yetkilendir" }, "deny": { - "message": "Deny" + "message": "Reddet" }, "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index dfcd104cf53..9bff79aa857 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -742,7 +742,7 @@ "message": "必须填写确认主密码。" }, "masterPasswordMinlength": { - "message": "主密码必须至少 $VALUE$ 个字符长度。", + "message": "主密码长度必须至少为 $VALUE$ 个字符。", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -1044,7 +1044,7 @@ "message": "前往网页 App 吗?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "您可以在 Bitwarden 网页应用上更改您的主密码。" + "message": "您可以在 Bitwarden 网页 App 上更改您的主密码。" }, "fingerprintPhrase": { "message": "指纹短语", @@ -1058,7 +1058,7 @@ "message": "转到网页版密码库" }, "getMobileApp": { - "message": "获取移动应用程序" + "message": "获取移动 App" }, "getBrowserExtension": { "message": "获取浏览器扩展" @@ -1247,13 +1247,13 @@ "message": "语言" }, "languageDesc": { - "message": "更改应用程序所使用的语言。重新启动后生效。" + "message": "更改应用程序所使用的语言。重启后生效。" }, "theme": { "message": "主题" }, "themeDesc": { - "message": "更改本应用程序的颜色主题。" + "message": "更改应用程序的颜色主题。" }, "dark": { "message": "深色", @@ -1665,7 +1665,7 @@ "message": "确认密码库导出" }, "exportWarningDesc": { - "message": "导出的密码库数据包含未加密格式。您不应该通过不安全的渠道(例如电子邮件)来存储或发送导出的文件。用完后请立即将其删除。" + "message": "此导出包含未加密格式的密码库数据。您不应该通过不安全的渠道(例如电子邮件)来存储或发送此导出文件。使用完后请立即将其删除。" }, "encExportKeyWarningDesc": { "message": "此导出将使用您账户的加密密钥来加密您的数据。如果您曾经轮换过账户的加密密钥,您应将其重新导出,否则您将无法解密导出的文件。" @@ -1711,7 +1711,7 @@ "message": "使用 PIN 码解锁" }, "setYourPinCode": { - "message": "设定您用来解锁 Bitwarden 的 PIN 码。您的 PIN 设置将在您完全注销本应用程序时被重置。" + "message": "设置用于解锁 Bitwarden 的 PIN 码。您的 PIN 设置将在您完全注销应用程序时被重置。" }, "pinRequired": { "message": "需要 PIN 码。" @@ -1753,7 +1753,7 @@ "message": "应用程序启动时要求使用触控 ID" }, "requirePasswordOnStart": { - "message": "应用程序启动时要求输入密码或 PIN 码" + "message": "App 启动时要求输入密码或 PIN 码" }, "recommendedForSecurity": { "message": "安全起见,推荐设置。" @@ -2402,7 +2402,7 @@ "message": "偏好设置" }, "appPreferences": { - "message": "应用设置(所有账户)" + "message": "应用程序设置(所有账户)" }, "accountSwitcherLimitReached": { "message": "已达到账户上限。请注销一个账户后再添加其他账户。" @@ -3369,7 +3369,7 @@ "message": "拒绝" }, "sshkeyApprovalTitle": { - "message": "确认 SSH 密钥的使用方式" + "message": "确认 SSH 密钥的使用" }, "sshkeyApprovalMessageInfix": { "message": "正在请求访问" @@ -3402,7 +3402,7 @@ "message": "设置两步登录" }, "newDeviceVerificationNoticeContentPage1": { - "message": "从 2025 年 02 月开始,Bitwarden 将向您的账户电子邮箱发送一个代码,以验证来自新设备的登录。" + "message": "从 2025 年 02 月起,当有来自新设备的登录时,Bitwarden 将向您的账户电子邮箱发送验证码。" }, "newDeviceVerificationNoticeContentPage2": { "message": "您可以设置两步登录作为保护账户的替代方法,或将您的电子邮箱更改为您可以访问的电子邮箱。" @@ -3411,7 +3411,7 @@ "message": "稍后提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "您能可靠地访问您的电子邮箱 $EMAIL$ 吗?", + "message": "您能可正常访问您的电子邮箱 $EMAIL$ 吗?", "placeholders": { "email": { "content": "$1", @@ -3423,7 +3423,7 @@ "message": "不,我不能" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "是的,我可以可靠地访问我的电子邮箱" + "message": "是的,我可以正常访问我的电子邮箱" }, "turnOnTwoStepLogin": { "message": "开启两步登录" From 860711337e461dc3c69c7444c32628c443ae3df5 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:34:23 +0000 Subject: [PATCH 061/270] Autosync the updated translations (#12712) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/fi/messages.json | 6 +- apps/browser/src/_locales/it/messages.json | 310 +++++++++--------- apps/browser/src/_locales/ja/messages.json | 12 +- apps/browser/src/_locales/tr/messages.json | 2 +- apps/browser/src/_locales/zh_CN/messages.json | 8 +- 5 files changed, 169 insertions(+), 169 deletions(-) diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 2c621dc4621..718c0631156 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2105,7 +2105,7 @@ "message": "Aikakatkaisutoiminnon vahvistus" }, "autoFillAndSave": { - "message": "Täytä automaattisesti ja tallenna" + "message": "Automaattitäytä ja tallenna" }, "fillAndSave": { "message": "Täytä ja tallenna" @@ -3482,7 +3482,7 @@ "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "Aika jäljellä, ennen kuin nykyinen TOTP vanhenee", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { @@ -4810,7 +4810,7 @@ "message": "Määritä kaksivaiheinen kirjautuminen" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden lähettää tilisi sähköpostiosoitteeseen koodin, jolla voit vahvistaa kirjautumiset uusista laitteista helmikuusta 2025 alkaen." }, "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." diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 711f62f4dea..6490a441832 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -20,16 +20,16 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nuovo in Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Accedi con passkey" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usa il Single Sign-On" }, "welcomeBack": { - "message": "Welcome back" + "message": "Bentornato" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -120,7 +120,7 @@ "message": "Copia password" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Copia passphrase" }, "copyNote": { "message": "Copia nota" @@ -153,13 +153,13 @@ "message": "Copia numero licenza" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Copia chiave privata" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Copia chiave pubblica" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Copia impronta" }, "copyCustomField": { "message": "Copia $FIELD$", @@ -177,7 +177,7 @@ "message": "Copia note" }, "fill": { - "message": "Fill", + "message": "Riempi", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +193,10 @@ "message": "Riempi automaticamente identità" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Riempi codice di verifica" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Riempi Codice di Verifica", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -239,7 +239,7 @@ "message": "Aggiungi elemento" }, "accountEmail": { - "message": "Account email" + "message": "Email dell'account" }, "requestHint": { "message": "Richiedi suggerimento" @@ -443,7 +443,7 @@ "message": "Genera password" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera passphrase" }, "regeneratePassword": { "message": "Rigenera password" @@ -530,7 +530,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "I requisiti di politica aziendale sono stati applicati alle opzioni del generatore.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -576,7 +576,7 @@ "message": "Note" }, "privateNote": { - "message": "Private note" + "message": "Nota privata" }, "note": { "message": "Nota" @@ -600,7 +600,7 @@ "message": "Avvia il sito web" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Apri sito web $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -633,7 +633,7 @@ "message": "Timeout della sessione" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "Timeout cassaforte" }, "otherOptions": { "message": "Altre opzioni" @@ -651,13 +651,13 @@ "message": "La tua cassaforte è bloccata. Verifica la tua identità per continuare." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Cassaforte bloccata" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Il tuo account è bloccato" }, "or": { - "message": "or" + "message": "o" }, "unlock": { "message": "Sblocca" @@ -852,7 +852,7 @@ "message": "Accedi" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Accedi a Bitwarden" }, "restartRegistration": { "message": "Riprova la registrazione" @@ -888,10 +888,10 @@ "message": "La verifica in due passaggi rende il tuo account più sicuro richiedendoti di verificare il tuo login usando un altro dispositivo come una chiave di sicurezza, app di autenticazione, SMS, telefonata, o email. Può essere abilitata nella cassaforte web su bitwarden.com. Vuoi visitare il sito?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Rendi il tuo account più sicuro impostando l'autenticazione a due fattori nell'app web di Bitwarden." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Aprire web app?" }, "editedFolder": { "message": "Cartella salvata" @@ -1005,7 +1005,7 @@ "message": "Mostra le identità nella sezione Scheda per riempirle automaticamente." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Clicca gli oggetti da riempire dalla sezione Cassaforte" }, "clearClipboard": { "message": "Cancella appunti", @@ -1126,7 +1126,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "Attenzione", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1206,7 +1206,7 @@ "message": "File" }, "fileToShare": { - "message": "File to share" + "message": "File da condividere" }, "selectFile": { "message": "Seleziona un file" @@ -1317,10 +1317,10 @@ "message": "Inserisci il codice di verifica a 6 cifre dalla tua app di autenticazione." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Timeout autenticazione" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "La sessione di autenticazione è scaduta. Accedi di nuovo." }, "enterVerificationCodeEmail": { "message": "Inserisci il codice di verifica a 6 cifre inviato a $EMAIL$.", @@ -1440,7 +1440,7 @@ "message": "URL del server" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL server autogestito", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1472,10 +1472,10 @@ "message": "Mostra suggerimenti di riempimento automatico nei campi del modulo" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Mostra identità come consigli" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Mostra carte come consigli" }, "showInlineMenuOnIconSelectionLabel": { "message": "Mostra suggerimenti quando l'icona è selezionata" @@ -1768,7 +1768,7 @@ "message": "Identità" }, "typeSshKey": { - "message": "SSH key" + "message": "Chiave SSH" }, "newItemHeader": { "message": "Nuovo $TYPE$", @@ -1801,13 +1801,13 @@ "message": "Cronologia delle password" }, "generatorHistory": { - "message": "Generator history" + "message": "Cronologia generatore" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Cancella cronologia generatore" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Se continui, tutte le voci verranno eliminate definitivamente dalla cronologia del generatore. Vuoi continuare?" }, "back": { "message": "Indietro" @@ -1846,7 +1846,7 @@ "message": "Note sicure" }, "sshKeys": { - "message": "SSH Keys" + "message": "Chiavi SSH" }, "clear": { "message": "Cancella", @@ -1929,10 +1929,10 @@ "message": "Cancella cronologia" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Niente da mostrare" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Non hai generato niente di recente" }, "remove": { "message": "Rimuovi" @@ -1993,16 +1993,16 @@ "message": "Sblocca con PIN" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "Imposta PIN" }, "setYourPinButton": { - "message": "Set PIN" + "message": "Imposta PIN" }, "setYourPinCode": { "message": "Imposta il tuo codice PIN per sbloccare Bitwarden. Le tue impostazioni PIN saranno resettate se esci completamente dall'app." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "Il tuo PIN sarà usato per sbloccare Bitwarden invece della password principale. Il PIN sarà ripristinato se ti disconnetterai completamente da Bitwarden." }, "pinRequired": { "message": "Codice PIN obbligatorio." @@ -2017,7 +2017,7 @@ "message": "Sblocca con i dati biometrici" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Sblocca con password principale" }, "awaitDesktop": { "message": "In attesa di conferma dal desktop" @@ -2029,7 +2029,7 @@ "message": "Blocca con la password principale al riavvio del browser" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Richiedi password principale al riavvio del browser" }, "selectOneCollection": { "message": "Devi selezionare almeno una raccolta." @@ -2067,7 +2067,7 @@ "message": "Azione timeout cassaforte" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Azione al timeout" }, "lock": { "message": "Blocca", @@ -2355,14 +2355,14 @@ "message": "Modifiche del dominio escluso salvate" }, "limitSendViews": { - "message": "Limit views" + "message": "Limita visualizzazioni" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "Nessuno potrà vedere questo Send al raggiungimento del limite.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ visualizzazioni rimaste", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2376,14 +2376,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "Dettagli Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "Testo" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Testo da condividere" }, "sendTypeFile": { "message": "File" @@ -2393,7 +2393,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "Nascondi testo come default" }, "expired": { "message": "Scaduto" @@ -2440,7 +2440,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "Sicuro di voler eliminare definitivamente questo Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -2451,7 +2451,7 @@ "message": "Data di eliminazione" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "Il Send sarà cancellato definitivamente in questa data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -2473,7 +2473,7 @@ "message": "Personalizzato" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "Richiedi ai destinatari una password opzionale per aprire questo Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -2500,11 +2500,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "Il Send sarà disponibile a chiunque con il link per la prossima ora.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "Il Send sarà disponibile a chiunque con il link per le prossime $HOURS$ ore.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2514,11 +2514,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "Il Send sarà disponibile a chiunque con il link per il prossimo giorno.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "Il Send sarà disponibile a chiunque con il link per i prossimi $DAYS$ giorni.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2536,11 +2536,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Pop out extension?", + "message": "Scollegare estensione?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { - "message": "To create a file Send, you need to pop out the extension to a new window.", + "message": "Per creare un file Send, devi scollegare l'estensione in una nuova finestra.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2553,7 +2553,7 @@ "message": "Per scegliere un file usando Safari, apri una nuova finestra cliccando questo banner." }, "popOut": { - "message": "Pop out" + "message": "Scollega" }, "sendFileCalloutHeader": { "message": "Prima di iniziare" @@ -2574,7 +2574,7 @@ "message": "Si è verificato un errore durante il salvataggio delle date di eliminazione e scadenza." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "Nascondi il tuo indirizzo email ai visualizzatori." }, "passwordPrompt": { "message": "Richiedi di inserire la password principale di nuovo per visualizzare questo elemento" @@ -2631,7 +2631,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "di $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2650,7 +2650,7 @@ "message": "Minuti" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "I requisiti di politica aziendale sono stati applicati alle opzioni di timeout" }, "vaultTimeoutPolicyInEffect": { "message": "Le politiche della tua organizzazione hanno impostato il timeout massimo consentito della tua cassaforte su $HOURS$ ore e $MINUTES$ minuti.", @@ -2666,7 +2666,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "Al massimo $HOURS$ ora/e e $MINUTES$ minuto/i.", "placeholders": { "hours": { "content": "$1", @@ -2679,7 +2679,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "Il timeout supera la restrizione impostata dalla tua organizzazione: massimo $HOURS$ ora/e e $MINUTES$ minuto/i", "placeholders": { "hours": { "content": "$1", @@ -2793,10 +2793,10 @@ "message": "Genera nome utente" }, "generateEmail": { - "message": "Generate email" + "message": "Genera email" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2810,7 +2810,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " Usa $RECOMMENDED$ caratteri o più per generare una password forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2820,7 +2820,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Usa $RECOMMENDED$ parole o più per generare una passphrase forte.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2861,11 +2861,11 @@ "message": "Genera un alias email con un servizio di inoltro esterno." }, "forwarderDomainName": { - "message": "Email domain", + "message": "Dominio email", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "Scegli un dominio supportato dal servizio selezionato", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -3068,25 +3068,25 @@ "message": "Invia notifica di nuovo" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Visualizza tutte le opzioni di accesso" }, "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Visualizza tutte le opzioni di accesso" }, "notificationSentDevice": { "message": "Una notifica è stata inviata al tuo dispositivo." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Una notifica è stata inviata al tuo dispositivo" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "Assicurati che il tuo account sia sbloccato e che la frase dell'impronta digitale corrisponda nell'altro dispositivo" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Sarai notificato una volta che la richiesta sarà approvata" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Bisogno di un'altra opzione?" }, "loginInitiated": { "message": "Accesso avviato" @@ -3182,16 +3182,16 @@ "message": "Si apre in una nuova finestra" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "Ricorda questo dispositivo per rendere immediati i futuri accessi" }, "deviceApprovalRequired": { "message": "Approvazione del dispositivo obbligatoria. Seleziona un'opzione di approvazione:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Approvazione dispositivo richiesta" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Seleziona un'opzione di approvazione sotto" }, "rememberThisDevice": { "message": "Ricorda questo dispositivo" @@ -3267,7 +3267,7 @@ "message": "Email utente mancante" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Email utente attiva non trovata. Logout in corso." }, "deviceTrusted": { "message": "Dispositivo fidato" @@ -3478,11 +3478,11 @@ "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "Codice di Verifica One-Time a tempo", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "Tempo rimasto prima che l'attuale TOTP scada", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { @@ -3711,10 +3711,10 @@ "message": "Passkey" }, "accessing": { - "message": "Accessing" + "message": "Accedendo a" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Accesso effettuato!" }, "passkeyNotCopied": { "message": "La passkey non sarà copiata" @@ -3741,7 +3741,7 @@ "message": "Nessun login corrispondente per questo sito" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "Cerca o salva la passkey come nuovo login" }, "confirm": { "message": "Conferma" @@ -4208,13 +4208,13 @@ "message": "Filtri" }, "filterVault": { - "message": "Filter vault" + "message": "Filtra cassaforte" }, "filterApplied": { - "message": "One filter applied" + "message": "Un filtro applicato" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "$COUNT$ filtri applicati", "placeholders": { "count": { "content": "$1", @@ -4328,7 +4328,7 @@ "message": "Abilita animazioni" }, "showAnimations": { - "message": "Show animations" + "message": "Mostra animazioni" }, "addAccount": { "message": "Aggiungi account" @@ -4546,13 +4546,13 @@ "message": "Posizione elemento" }, "fileSend": { - "message": "File Send" + "message": "Send di File" }, "fileSends": { "message": "Send File" }, "textSend": { - "message": "Text Send" + "message": "Send di Testo" }, "textSends": { "message": "Send Testo" @@ -4570,7 +4570,7 @@ "message": "Mostra il numero di suggerimenti di riempimento automatico sull'icona dell'estensione" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "Mostra azioni di copia rapida nella Cassaforte" }, "systemDefault": { "message": "Predefinito del sistema" @@ -4579,37 +4579,37 @@ "message": "I requisiti della policy aziendale sono stati applicati a questa impostazione" }, "sshPrivateKey": { - "message": "Private key" + "message": "Chiave privata" }, "sshPublicKey": { - "message": "Public key" + "message": "Chiave pubblica" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Impronta digitale" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipo di chiave" }, "sshKeyAlgorithmED25519": { "message": "ED25519" }, "sshKeyAlgorithmRSA2048": { - "message": "RSA 2048-Bit" + "message": "RSA a 2048 bit" }, "sshKeyAlgorithmRSA3072": { - "message": "RSA 3072-Bit" + "message": "RSA a 3072 bit" }, "sshKeyAlgorithmRSA4096": { - "message": "RSA 4096-Bit" + "message": "RSA a 4096 bit" }, "retry": { - "message": "Retry" + "message": "Riprova" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Il timeout personalizzato minimo è 1 minuto." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Sono disponibili ulteriori contenuti" }, "fileSavedToDevice": { "message": "File salvato sul dispositivo. Gestisci dai download del dispositivo." @@ -4642,22 +4642,22 @@ "message": "Non hai i permessi per modificare questo elemento" }, "authenticating": { - "message": "Authenticating" + "message": "Autenticazione" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "Riempi password generata", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "Password rigenerata", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Salvare il login su Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Spazio", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { @@ -4669,157 +4669,157 @@ "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Punto esclamativo", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Chiocciola", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Cancelletto", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Simbolo del dollaro", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Segno di percentuale", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "Accento circonflesso", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "E commerciale", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "Asterisco", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Parentesi sinistra", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Parentesi destra", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Trattino basso", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Trattino", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Più", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "Uguale", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Parentesi graffa aperta", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Parentesi graffa chiusa", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Parentesi quadra aperta", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Parentesi quadra chiusa", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Barra verticale", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Barra rovesciata", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Due punti", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Punto e virgola", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Doppi apici", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Apostrofo", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Minore", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Maggiore", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Virgola", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Punto", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Punto interrogativo", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Slash", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Minuscolo" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Maiuscolo" }, "generatedPassword": { - "message": "Generated password" + "message": "Password generata" }, "compactMode": { - "message": "Compact mode" + "message": "Modalità compatta" }, "beta": { "message": "Beta" }, "importantNotice": { - "message": "Important notice" + "message": "Avviso importante" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Imposta accesso in due passaggi" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden invierà un codice all'email del tuo account per verificare gli accessi da nuovi dispositivi a partire da febbraio 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": "Puoi impostare l'accesso in due passaggi come modo alternativo per proteggere il tuo account, o cambiare la tua e-mail in una alla quale puoi accedere." }, "remindMeLater": { - "message": "Remind me later" + "message": "Ricordamelo più tardi" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Riesci ancora ad accedere a questa email, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -4828,24 +4828,24 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No, non riesco" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Sì, riesco ad accedere a questa email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Attiva accesso in due passaggi" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Cambia l'email dell'account" }, "extensionWidth": { - "message": "Extension width" + "message": "Larghezza estensione" }, "wide": { - "message": "Wide" + "message": "Larga" }, "extraWide": { - "message": "Extra wide" + "message": "Molto larga" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 0ffe01e7992..42dd73020ec 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -1005,7 +1005,7 @@ "message": "自動入力を簡単にするために、タブページに ID アイテムを表示します" }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "保管庫で、自動入力するアイテムをクリックしてください" }, "clearClipboard": { "message": "クリップボードの消去", @@ -4810,10 +4810,10 @@ "message": "2段階認証を設定する" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "Bitwarden は2025年2月以降、新しいデバイスからのログイン時にアカウントのメールアドレスに確認コードを送信します。" }, "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": "代わりに2段階認証によるログインでアカウントを保護するか、メールアドレスをあなたがアクセスできるものに変更できます。" }, "remindMeLater": { "message": "後で再通知" @@ -4831,13 +4831,13 @@ "message": "いいえ、違います。" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "はい、メールアドレスには私が確実にアクセスできます" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "2段階認証によるログインを有効にする" }, "changeAcctEmail": { - "message": "Change account email" + "message": "アカウントのメールアドレスを変更する" }, "extensionWidth": { "message": "拡張機能の幅" diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index a4c064e0394..e1236b3f86d 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -4154,7 +4154,7 @@ "message": "Ek bilgiler" }, "itemHistory": { - "message": "Öğe geçmişi" + "message": "Kayıt geçmişi" }, "lastEdited": { "message": "Son düzenlenme" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 9af42f75e08..69c1194af47 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -428,7 +428,7 @@ "description": "Short for 'credential generator'." }, "passGenInfo": { - "message": "自动生成安全可靠唯一的登录密码。" + "message": "自动为您的登录生成强大且唯一的密码。" }, "bitWebVaultApp": { "message": "Bitwarden 网页 App" @@ -888,7 +888,7 @@ "message": "两步登录要求您从其他设备(例如安全钥匙、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" }, "twoStepLoginConfirmationContent": { - "message": "通过在 Bitwarden 网页 App 中设置两步登录,可以使您的账户更加安全。" + "message": "在 Bitwarden 网页 App 中设置两步登录,让您的账户更加安全。" }, "twoStepLoginConfirmationTitle": { "message": "前往网页 App 吗?" @@ -2123,7 +2123,7 @@ "message": "您仍然想要填充此登录信息吗?" }, "autofillIframeWarning": { - "message": "该表单由不同于您保存的登录的 URI 域名托管。选择「确定」以自动填充,或选择「取消」停止填充。" + "message": "该表单由与您保存的登录 URI 不同的域名托管。选择「确定」继续自动填充,或选择「取消」停止自动填充。" }, "autofillIframeWarningTip": { "message": "要防止以后出现此警告,请将此站点的 URI $HOSTNAME$ 保存到您的 Bitwarden 登录项目中。", @@ -3982,7 +3982,7 @@ "message": "自动填充建议" }, "autofillSuggestionsTip": { - "message": "保存此站点的登录项目用来自动填充" + "message": "将此站点保存为登录项目以用于自动填充" }, "yourVaultIsEmpty": { "message": "您的密码库是空的" From e75a38c438270fd386e816bb4414b7cae630c30b Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:34:47 +0000 Subject: [PATCH 062/270] Autosync the updated translations (#12713) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/az/messages.json | 8 +- apps/web/src/locales/da/messages.json | 8 +- apps/web/src/locales/de/messages.json | 14 +-- apps/web/src/locales/en_IN/messages.json | 6 +- apps/web/src/locales/ja/messages.json | 146 +++++++++++------------ apps/web/src/locales/lv/messages.json | 8 +- apps/web/src/locales/sk/messages.json | 14 +-- apps/web/src/locales/zh_CN/messages.json | 26 ++-- 8 files changed, 115 insertions(+), 115 deletions(-) diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 09785738464..b8111a0e997 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -9272,16 +9272,16 @@ "message": "Güncəlllənən vergi məlumatı" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Yararsız vergi kimliyi, bunun xəta olduğunu düşünürsünüzsə, dəstək komandası ilə əlaqə saxlayın." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Vergi kimliyi nömrənizi doğrulaya bilmədik, bunun xəta olduğunu düşünürsünüzsə, dəstək komandası ilə əlaqə saxlayın." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Yararsız vergi kimliyi, bunun xəta olduğunu düşünürsünüzsə, dəstək komandası ilə əlaqə saxlayın." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Faktura önizləməsi zamanı bir xəta baş verdi. Lütfən daha sonra yenidən sınayın." }, "unverified": { "message": "Doğrulanmayıb" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index b2b3b0491c3..f71e1feeb3b 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -9272,16 +9272,16 @@ "message": "Opdaterede momsoplysninger" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Ugyldigt moms-ID. Mener man, at dette er en fejl, kontakt venligst supporten." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Moms-ID kan ikke bekræftes. Mener man, at dette er en fejl, kontakt venligst supporten." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Ugyldigt moms-ID. Mener man, at dette er en fejl, kontakt venligst supporten." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "En fejl opstod under forhåndsvisning af fakturaen. Forsøg igen senere." }, "unverified": { "message": "Ubekræftet" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 4596dd88b3a..93422471457 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -9272,16 +9272,16 @@ "message": "Steuerinformationen aktualisiert" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Ungültige Steuer-ID. Wenn du glaubst, dass dies ein Fehler ist, wende dich bitte an den Support." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Wir konnten deine Steuer-ID nicht überprüfen. Wenn du glaubst, dass dies ein Fehler ist, kontaktiere bitte den Support." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Ungültige Steuer-ID. Wenn du glaubst, dass dies ein Fehler ist, wende dich bitte an den Support." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Bei der Vorschau der Rechnung ist ein Fehler aufgetreten. Bitte versuche es später erneut." }, "unverified": { "message": "Nicht verifiziert" @@ -10021,7 +10021,7 @@ "message": "Der Name der Organisation darf 50 Zeichen nicht überschreiten." }, "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "message": "Dein Abonnement wird bald verlängert. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$ um deine Verlängerung vor dem $RENEWAL_DATE$ zu bestätigen.", "placeholders": { "reseller": { "content": "$1", @@ -10034,7 +10034,7 @@ } }, "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "message": "Eine Rechnung für dein Abonnement wurde am $ISSUED_DATE$ ausgestellt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $DUE_DATE$ zu bestätigen.", "placeholders": { "reseller": { "content": "$1", @@ -10051,7 +10051,7 @@ } }, "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "message": "Die Rechnung für dein Abonnement wurde nicht bezahlt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $GRACE_PERIOD_END$ zu bestätigen.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 131b6b36383..ec67bb192c9 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -9272,13 +9272,13 @@ "message": "Updated tax information" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Invalid Aadhaar ID, if you believe this is an error please contact support." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "We were unable to validate your Aadhaar ID, if you believe this is an error please contact support." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Invalid Aadhaar ID, if you believe this is an error please contact support." }, "billingPreviewInvoiceError": { "message": "An error occurred while previewing the invoice. Please try again later." diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 1383ce49170..6b3b6f13b46 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -3,22 +3,22 @@ "message": "すべてのアプリ" }, "criticalApplications": { - "message": "Critical applications" + "message": "きわめて重要なアプリ" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "アクセス インテリジェンス" }, "riskInsights": { - "message": "Risk Insights" + "message": "リスク分析" }, "passwordRisk": { - "message": "Password Risk" + "message": "パスワードのリスク" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." + "message": "危険なパスワード(強度が低い、流出済み、再利用)を、アプリをまたいで調査します。特に重要なアプリを選択して、危険なパスワードに優先対応するようユーザーに促しましょう。" }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "データの最終更新: $DATE$", "placeholders": { "date": { "content": "$1", @@ -33,10 +33,10 @@ "message": "メンバーを削除" }, "restoreMembers": { - "message": "Restore members" + "message": "メンバーを復元" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "組織へのアクセスを復元できません" }, "allApplicationsWithCount": { "message": "すべてのアプリ ($COUNT$)", @@ -48,10 +48,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "新しいログインアイテムを作成" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "特に重要なアプリ ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -69,7 +69,7 @@ } }, "noAppsInOrgTitle": { - "message": "No applications found in $ORG NAME$", + "message": "$ORG NAME$ にアプリが見つかりませんでした", "placeholders": { "org name": { "content": "$1", @@ -78,22 +78,22 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "ユーザーがログイン情報を保存すると、アプリがここに表示され、リスクの高いパスワードがあれば表示されます。特に重要なアプリはマークして、パスワードを更新するようにユーザーに通知しましょう。" }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "重要なアプリとしてマークしたものがありません" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "特に重要なアプリケーションを選択して、危険なパスワードを発見し、ユーザーにパスワードを変更するよう通知しましょう。" }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "重要なアプリをマークする" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "重要なアプリとしてマーク" }, "appsMarkedAsCritical": { - "message": "Apps marked as critical" + "message": "重要なアプリとしてマークされました" }, "application": { "message": "アプリ" @@ -102,13 +102,13 @@ "message": "リスクがあるパスワード" }, "requestPasswordChange": { - "message": "Request password change" + "message": "パスワードの変更を要求する" }, "totalPasswords": { "message": "合計パスワード数" }, "searchApps": { - "message": "Search applications" + "message": "アプリを検索" }, "atRiskMembers": { "message": "リスクがあるメンバー" @@ -469,7 +469,7 @@ "message": "パスワードの自動生成" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "パスフレーズを生成" }, "checkPassword": { "message": "パスワードが漏洩していないか確認する" @@ -572,7 +572,7 @@ "message": "セキュアメモ" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 鍵" }, "typeLoginPlural": { "message": "ログイン" @@ -733,11 +733,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "パスフレーズをコピー", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "パスワードをコピーしました" }, "copyUsername": { "message": "ユーザー名のコピー", @@ -994,7 +994,7 @@ "message": "Bitwarden アプリで「デバイスでログイン」の設定をする必要があります。別のオプションが必要ですか?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "別の選択肢が必要ですか?" }, "loginWithMasterPassword": { "message": "マスターパスワードでログイン" @@ -1009,13 +1009,13 @@ "message": "別のログイン方法を使用する" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "パスキーでログイン" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "シングルサインオンを使用する" }, "welcomeBack": { - "message": "Welcome back" + "message": "ようこそ" }, "invalidPasskeyPleaseTryAgain": { "message": "無効なパスキーです。もう一度やり直してください。" @@ -1099,7 +1099,7 @@ "message": "アカウントの作成" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Bitwarden は初めてですか?" }, "setAStrongPassword": { "message": "強力なパスワードを設定する" @@ -1117,13 +1117,13 @@ "message": "ログイン" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden にログイン" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "認証のタイムアウト" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "認証セッションの有効期限が切れました。ログイン操作を最初からやり直してください。" }, "verifyIdentity": { "message": "本人確認" @@ -1294,7 +1294,7 @@ "message": "このコレクション内のアイテムをすべて表示する権限がありません。" }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "このコレクションの利用権限がありません" }, "noCollectionsInList": { "message": "表示するコレクションがありません" @@ -1321,10 +1321,10 @@ "message": "デバイスに通知を送信しました。" }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "お使いのデバイスに通知が送信されました" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "アカウントがロック解除されていることと、フィンガープリントフレーズが他の端末と一致していることを確認してください" }, "versionNumber": { "message": "バージョン $VERSION_NUMBER$", @@ -1658,25 +1658,25 @@ "message": "パスワードの履歴" }, "generatorHistory": { - "message": "Generator history" + "message": "生成履歴" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "生成履歴を消去" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "続行すると、すべてのエントリは生成履歴から完全に削除されます。続行してもよろしいですか?" }, "noPasswordsInList": { "message": "表示するパスワードがありません" }, "clearHistory": { - "message": "Clear history" + "message": "履歴を消去" }, "nothingToShow": { - "message": "Nothing to show" + "message": "表示するものがありません" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "最近生成したものはありません" }, "clear": { "message": "消去する", @@ -1786,7 +1786,7 @@ "message": "これらの操作はやり直せないため注意してください!" }, "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" + "message": "この操作は元に戻せないため注意してください!" }, "deauthorizeSessions": { "message": "セッションの承認を取り消す" @@ -1801,7 +1801,7 @@ "message": "全てのセッションを無効化" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "このアカウントは $ORGANIZATIONNAME$ が所有しています", "placeholders": { "organizationName": { "content": "$1", @@ -3430,7 +3430,7 @@ } }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "すべてのログインオプションを表示" }, "viewAllLoginOptions": { "message": "すべてのログインオプションを表示" @@ -3883,13 +3883,13 @@ "message": "ブラウザを更新" }, "generatingRiskInsights": { - "message": "Generating your risk insights..." + "message": "リスク分析を生成しています..." }, "updateBrowserDesc": { "message": "サポートされていないブラウザを使用しています。ウェブ保管庫が正しく動作しないかもしれません。" }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "無料体験はあと $COUNT$ 日で終了します。", "placeholders": { "count": { "content": "$1", @@ -3898,7 +3898,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$ 様、無料体験はあと $COUNT$ 日で終了します。", "placeholders": { "count": { "content": "$2", @@ -3911,7 +3911,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$ 様、無料体験は明日で終了します。", "placeholders": { "organization": { "content": "$1", @@ -3920,10 +3920,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "無料体験は明日終了します。" }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$ 様、無料体験は本日終了します。", "placeholders": { "organization": { "content": "$1", @@ -3932,16 +3932,16 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "無料体験は本日で終了します。" }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "支払い方法を追加するにはここをクリックしてください。" }, "joinOrganization": { "message": "組織に参加" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "$ORGANIZATIONNAME$ に参加", "placeholders": { "organizationName": { "content": "$1", @@ -4492,7 +4492,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "リクエストが承認されると通知されます" }, "free": { "message": "無料", @@ -4725,10 +4725,10 @@ "message": "組織のシングルサインオンポータルを使用してログインします。開始するには組織の識別子を入力してください。" }, "singleSignOnEnterOrgIdentifier": { - "message": "Enter your organization's SSO identifier to begin" + "message": "組織の SSO ID を入力して開始します" }, "singleSignOnEnterOrgIdentifierText": { - "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + "message": "SSO プロバイダーでログインするには、組織の SSO ID を入力して開始します。新しいデバイスからログインする際に、この SSO ID を入力する必要がある場合があります。" }, "enterpriseSingleSignOn": { "message": "組織のシングルサインオン" @@ -4798,7 +4798,7 @@ "message": "ユーザーが他の組織に参加できないように制限します。" }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "メンバーに対し、他の組織への参加を制限します。ドメイン認証を有効にしている組織では、このポリシーは必須となります。" }, "singleOrgBlockCreateMessage": { "message": "現在の組織には、複数の組織に参加することを許可していないポリシーがあります。 組織の管理者に連絡するか、別の Bitwarden アカウントから登録してください。" @@ -4807,7 +4807,7 @@ "message": "オーナーまたは管理者でなく、すでに他の組織のメンバーであるメンバーは組織から削除されます。" }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "ポリシーに準拠していないメンバーは、他のすべての組織から退出するまで失効状態になります。これは管理者には適用されず、管理者はコンプライアンスが満たされたメンバーを復元できます。" }, "requireSso": { "message": "シングルサインオン認証" @@ -5635,10 +5635,10 @@ "message": "除外します。このアクションには適用されません。" }, "nonCompliantMembersTitle": { - "message": "Non-compliant members" + "message": "非準拠のメンバー" }, "nonCompliantMembersError": { - "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + "message": "単一組織ポリシーまたは2段階ログインポリシーに準拠していないメンバーは、ポリシー要件を遵守するまで復元できません。" }, "fingerprint": { "message": "指紋" @@ -6437,7 +6437,7 @@ "message": "エンティティ ID が URL でない場合は必須です。" }, "offerNoLongerValid": { - "message": "This offer is no longer valid. Contact your organization administrators for more information." + "message": "このオファーは無効になりました。詳しくは組織の管理者にお問い合わせください。" }, "openIdOptionalCustomizations": { "message": "オプションのカスタマイズ" @@ -6529,10 +6529,10 @@ "message": "ユーザー名を生成" }, "generateEmail": { - "message": "Generate email" + "message": "メールアドレスを生成" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6546,7 +6546,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " 強力なパスワードを生成するには、 $RECOMMENDED$ 文字以上を使用してください。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6556,7 +6556,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " 強力なパスフレーズを生成するには、 $RECOMMENDED$ 単語以上を使用してください。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6671,11 +6671,11 @@ "message": "外部転送サービスを使用してメールエイリアスを生成します。" }, "forwarderDomainName": { - "message": "Email domain", + "message": "メールアドレスのドメイン", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "選択したサービスでサポートされているドメインを選択してください", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { @@ -8101,16 +8101,16 @@ "message": "ログイン開始" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "このデバイスを記憶して今後のログインをシームレスにする" }, "deviceApprovalRequired": { "message": "デバイスの承認が必要です。以下から承認オプションを選択してください:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "デバイスの承認が必要です" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "以下の承認オプションを選択してください" }, "rememberThisDevice": { "message": "このデバイスを記憶する" @@ -8342,7 +8342,7 @@ "message": "ユーザーのメールアドレスがありません" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "アクティブなユーザーメールアドレスが見つかりません。ログアウトします。" }, "deviceTrusted": { "message": "信頼されたデバイス" @@ -8440,10 +8440,10 @@ "message": "組織のコレクションに関する挙動を管理します" }, "limitCollectionCreationDesc": { - "message": "Limit collection creation to owners and admins" + "message": "コレクションの作成を所有者と管理者のみに制限" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "コレクションの削除を所有者と管理者のみに制限" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "所有者と管理者はすべてのコレクションとアイテムを管理できます" @@ -8491,7 +8491,7 @@ "message": "サーバー URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自己ホスト型サーバー URL", "description": "Label for field requesting a self-hosted integration service URL" }, "aliasDomain": { diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 6546b248914..0b830e6dc9a 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -9272,16 +9272,16 @@ "message": "Atjaunināta nodokļu informācija" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Nederīgs nodokļu identifikators. Ja ir pārliecība, ka tā ir kļūda, lūgums sazināties ar atbalstu." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Mēs nevarējām pārbaudīt nodokļu identifikatoru. Ja ir pārliecība, ka tā ir kļūda, lūgums sazināties ar atbalstu." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Nederīgs nodokļu identifikators. Ja ir pārliecība, ka tā ir kļūda, lūgums sazināties ar atbalstu." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Rēķina priekšskatīšanas laikā atgadījās kļūda. Lūgums vēlāk mēģināt vēlreiz." }, "unverified": { "message": "Neapliecināts" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 4c47afaa63d..dbffa25048c 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -9272,16 +9272,16 @@ "message": "Aktualizované daňové informácie" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Neplatne číslo pre DPH, ak myslíte že ide o chybu, kontaktujte prosím zákaznícku podporu." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "Nepodarilo sa nám overiť vaše číslo pre DPH, ak myslíte že ide o chybu, kontaktujte prosím zákaznícku podporu." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "Neplatne číslo pre DPH, ak myslíte že ide o chybu, kontaktujte prosím zákaznícku podporu." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "Pri vytváraní náhľadu faktúry nastala chyba. Prosím skúste to neskor." }, "unverified": { "message": "Neoverený" @@ -10021,7 +10021,7 @@ "message": "Meno organizácie nemôže mať viac ako 50 znakov." }, "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "message": "Vaše predplatné sa čoskoro obnoví. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10034,7 +10034,7 @@ } }, "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "message": "Faktúra za vaše predplatné bola vystavená dňa $ISSUED_DATE$. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie predplatného pred $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10051,7 +10051,7 @@ } }, "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "message": "Faktúra za vaše predplatné nebola uhradená. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index a7d73801372..f293adcf62a 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1006,7 +1006,7 @@ "message": "保持此窗口打开然后按照浏览器的提示操作。" }, "useADifferentLogInMethod": { - "message": "使用不同的登录方式" + "message": "使用其他登录方式" }, "logInWithPasskey": { "message": "使用通行密钥登录" @@ -1997,13 +1997,13 @@ "message": "域名规则" }, "domainRulesDesc": { - "message": "如果您在多个不同网站之间使用同一个登陆信息,您可以把这些网站标记为「通用」。Bitwarden 会为您设置「全局」域名。" + "message": "如果您在多个不同网站域名中使用同一个登录信息,您可以把这些网站标记为「等效」。「全局」域名是由 Bitwarden 为您预先创建的域名。" }, "globalEqDomains": { - "message": "全局通用域名" + "message": "全局等效域名" }, "customEqDomains": { - "message": "自定义通用域名" + "message": "自定义等效域名" }, "exclude": { "message": "排除" @@ -2039,7 +2039,7 @@ "message": "强制两步登录" }, "twoStepLoginDesc": { - "message": "在登录时要求使用额外的步骤来保护您的账户。" + "message": "在登录时要求执行额外的步骤来保护您的账户。" }, "twoStepLoginTeamsDesc": { "message": "为您的组织启用两步登录。" @@ -2055,7 +2055,7 @@ "message": "要实施 Duo 方式的两步登录,请使用下面的选项。" }, "twoStepLoginOrganizationSsoDesc": { - "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序实施了。" + "message": "如果您已设置或计划设置 SSO,两步登录可能已经通过您的身份提供程序强制实施了。" }, "twoStepLoginRecoveryWarning": { "message": "启用两步登录可能会将您永久锁定在 Bitwarden 账户之外。如果您无法使用常规的两步登录提供程序(例如您丢失了设备),则可以使用恢复代码访问您的账户。如果您失去对您账户的访问,Bitwarden 支持也无法帮助您。我们建议您记下或打印恢复代码,并将其妥善保管。" @@ -3294,7 +3294,7 @@ "message": "管理员" }, "adminDesc": { - "message": "管理组织访问权限,所有集合,成员,报告以及安全设置" + "message": "管理组织的访问权限,所有集合、成员、报告,以及安全设置" }, "user": { "message": "用户" @@ -4304,7 +4304,7 @@ "message": "升级组织" }, "upgradeOrganizationDesc": { - "message": "本功能对免费组织不可用。切换到付费计划以解锁更多功能。" + "message": "此功能不适用于免费组织。请切换到付费计划以解锁更多功能。" }, "createOrganizationStep1": { "message": "创建组织:第一步" @@ -4514,7 +4514,7 @@ "message": "您的 API 密钥可用于在 Bitwarden CLI 中进行身份验证。" }, "userApiKeyWarning": { - "message": "您的 API 密钥是另一套等效的身份验证机制。请严格保密。" + "message": "您的 API 密钥是一种替代身份验证机制。请严格保密。" }, "oauth2ClientCredentials": { "message": "OAuth 2.0 客户端凭据", @@ -5998,16 +5998,16 @@ "message": "最小入站签名算法" }, "spWantAssertionsSigned": { - "message": "希望断言被签名" + "message": "要求使用签名的断言" }, "spValidateCertificates": { "message": "验证证书" }, "spUniqueEntityId": { - "message": "设置一个唯一的 SP 实体 ID" + "message": "设置专属的 SP 实体 ID" }, "spUniqueEntityIdDesc": { - "message": "生成您的组织独有的标识符" + "message": "为您的组织生成专属的标识符" }, "idpEntityId": { "message": "实体 ID" @@ -8077,7 +8077,7 @@ "message": "忽略" }, "notAvailableForFreeOrganization": { - "message": "免费组织不能使用此功能。请联系您的组织所有者寻求升级。" + "message": "此功能不适用于免费组织。请联系您的组织所有者寻求升级。" }, "smProjectSecretsNoItemsNoAccess": { "message": "请联系您的组织的管理员来管理此工程的机密。", From 828a7fe3396bad3ec04b013816d818d4e30da9c9 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Mon, 6 Jan 2025 08:42:38 -0800 Subject: [PATCH 063/270] [PM-15557] Log the Cipher_ClientViewed event when opening the VaultItemDialog (#12669) --- .../vault-item-dialog/vault-item-dialog.component.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 02dc5ef48bb..a9ff49c5791 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -9,9 +9,11 @@ import { map } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -237,6 +239,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { private premiumUpgradeService: PremiumUpgradePromptService, private cipherAuthorizationService: CipherAuthorizationService, private apiService: ApiService, + private eventCollectionService: EventCollectionService, ) { this.updateTitle(); } @@ -257,6 +260,13 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { [this.params.activeCollectionId], this.params.isAdminConsoleAction, ); + + await this.eventCollectionService.collect( + EventType.Cipher_ClientViewed, + this.cipher.id, + false, + this.cipher.organizationId, + ); } this.performingInitialLoad = false; From ec21e8db592d929f55dae2579dc48825d44b3028 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:44:36 +0100 Subject: [PATCH 064/270] Add missing credit card number pipe (#12508) Co-authored-by: Daniel James Smith --- .../cipher-view/card-details/card-details-view.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index d805408b385..fff771b6465 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -22,7 +22,7 @@ readonly bitInput type="password" - [value]="card.number" + [value]="card.number | creditCardNumber: cipher.card.brand" aria-readonly="true" data-testid="cardholder-number" class="tw-font-mono" From c349ea95c6a2608bee3296a60805a98e3d24348f Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:11:31 +0100 Subject: [PATCH 065/270] Remove v1 popout component (#12518) Co-authored-by: Daniel James Smith --- .../platform/popup/components/pop-out.component.html | 7 +------ .../src/platform/popup/components/pop-out.component.ts | 10 +--------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/apps/browser/src/platform/popup/components/pop-out.component.html b/apps/browser/src/platform/popup/components/pop-out.component.html index c3f1f8ca150..3097f6e30d3 100644 --- a/apps/browser/src/platform/popup/components/pop-out.component.html +++ b/apps/browser/src/platform/popup/components/pop-out.component.html @@ -1,9 +1,4 @@ - - - - + + + + + + + diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts new file mode 100644 index 00000000000..abf74371912 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -0,0 +1,69 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + ButtonModule, + DialogModule, + DialogService, + ItemModule, + LinkModule, +} from "@bitwarden/components"; +import { + CredentialGeneratorHistoryDialogComponent, + GeneratorModule, +} from "@bitwarden/generator-components"; +import { CipherFormGeneratorComponent } from "@bitwarden/vault"; + +type CredentialGeneratorParams = { + onCredentialGenerated: (value?: string) => void; + type: "password" | "username"; +}; + +@Component({ + standalone: true, + selector: "credential-generator-dialog", + templateUrl: "credential-generator-dialog.component.html", + imports: [ + CipherFormGeneratorComponent, + CommonModule, + DialogModule, + ButtonModule, + JslibModule, + GeneratorModule, + ItemModule, + LinkModule, + ], +}) +export class CredentialGeneratorDialogComponent { + credentialValue?: string; + + constructor( + @Inject(DIALOG_DATA) protected data: CredentialGeneratorParams, + private dialogService: DialogService, + ) {} + + applyCredentials = () => { + this.data.onCredentialGenerated(this.credentialValue); + }; + + clearCredentials = () => { + this.data.onCredentialGenerated(); + }; + + onCredentialGenerated = (value: string) => { + this.credentialValue = value; + }; + + openHistoryDialog = () => { + // open history dialog + this.dialogService.open(CredentialGeneratorHistoryDialogComponent); + }; + + static open = (dialogService: DialogService, data: CredentialGeneratorParams) => { + dialogService.open(CredentialGeneratorDialogComponent, { + data, + }); + }; +} diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index 67b69be7d1b..f375b303024 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -20,7 +20,9 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -40,6 +42,7 @@ import { invokeMenu, RendererMenuItem } from "../../../utils"; import { AddEditComponent } from "./add-edit.component"; import { AttachmentsComponent } from "./attachments.component"; import { CollectionsComponent } from "./collections.component"; +import { CredentialGeneratorDialogComponent } from "./credential-generator-dialog.component"; import { FolderAddEditComponent } from "./folder-add-edit.component"; import { PasswordHistoryComponent } from "./password-history.component"; import { ShareComponent } from "./share.component"; @@ -107,6 +110,7 @@ export class VaultComponent implements OnInit, OnDestroy { private apiService: ApiService, private dialogService: DialogService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -622,11 +626,29 @@ export class VaultComponent implements OnInit, OnDestroy { } async openGenerator(comingFromAddEdit: boolean, passwordType = true) { - // FIXME: Will need to be extended to use the cipher-form-generator component introduced with https://github.com/bitwarden/clients/pull/11350 - if (this.modal != null) { - this.modal.close(); + const isGeneratorSwapEnabled = await this.configService.getFeatureFlag( + FeatureFlag.GeneratorToolsModernization, + ); + + if (isGeneratorSwapEnabled) { + CredentialGeneratorDialogComponent.open(this.dialogService, { + onCredentialGenerated: (value?: string) => { + if (this.addEditComponent != null) { + this.addEditComponent.markPasswordAsDirty(); + if (passwordType) { + this.addEditComponent.cipher.login.password = value ?? ""; + } else { + this.addEditComponent.cipher.login.username = value ?? ""; + } + } + }, + type: passwordType ? "password" : "username", + }); + return; } + // TODO: Legacy code below, remove once the new generator is fully implemented + // https://bitwarden.atlassian.net/browse/PM-7121 const cipher = this.addEditComponent?.cipher; const loginType = cipher != null && cipher.type === CipherType.Login && cipher.login != null; From ce07e408eaad52194ed53235a942cc24579de519 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:46:59 -0800 Subject: [PATCH 067/270] [PM-13028] - add fixed width to vault list icon (#12644) * add fixed width to vault list icon * justify start * don't display totp capture when in popout * Revert "don't display totp capture when in popout" This reverts commit f50b0a6caf4cb23d2af8521f51b9c772336782ac. --- .../vault-list-items-container.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 72ac590c779..b5d92a386b3 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 @@ -34,7 +34,9 @@ " class="{{ itemHeightClass }}" > - +
    + +
    {{ cipher.name }} Date: Mon, 6 Jan 2025 13:49:33 -0500 Subject: [PATCH 068/270] [CL-506] Upgrade to Angular 18 (#12218) --- apps/web/src/app/app.module.ts | 4 +- apps/web/src/app/shared/shared.module.ts | 6 +- apps/web/webpack.config.js | 2 +- .../bit-web/src/app/app.module.ts | 4 +- package-lock.json | 4044 +++++++++-------- package.json | 40 +- 6 files changed, 2148 insertions(+), 1952 deletions(-) diff --git a/apps/web/src/app/app.module.ts b/apps/web/src/app/app.module.ts index 2a67232e3db..22fd745eab8 100644 --- a/apps/web/src/app/app.module.ts +++ b/apps/web/src/app/app.module.ts @@ -3,7 +3,7 @@ import { LayoutModule } from "@angular/cdk/layout"; import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { InfiniteScrollModule } from "ngx-infinite-scroll"; +import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { AppComponent } from "./app.component"; import { CoreModule } from "./core"; @@ -23,7 +23,7 @@ import { WildcardRoutingModule } from "./wildcard-routing.module"; BrowserAnimationsModule, FormsModule, CoreModule, - InfiniteScrollModule, + InfiniteScrollDirective, DragDropModule, LayoutModule, OssRoutingModule, diff --git a/apps/web/src/app/shared/shared.module.ts b/apps/web/src/app/shared/shared.module.ts index 1b04583a395..8f44d8a4bf5 100644 --- a/apps/web/src/app/shared/shared.module.ts +++ b/apps/web/src/app/shared/shared.module.ts @@ -3,7 +3,7 @@ import { CommonModule, DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { RouterModule } from "@angular/router"; -import { InfiniteScrollModule } from "ngx-infinite-scroll"; +import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -49,7 +49,7 @@ import "./locales"; DragDropModule, FormsModule, ReactiveFormsModule, - InfiniteScrollModule, + InfiniteScrollDirective, RouterModule, JslibModule, @@ -86,7 +86,7 @@ import "./locales"; DragDropModule, FormsModule, ReactiveFormsModule, - InfiniteScrollModule, + InfiniteScrollDirective, RouterModule, JslibModule, diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index df325015aad..9373308c112 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -256,7 +256,7 @@ const devServer = 'sha256-JVRXyYPueLWdwGwY9m/7u4QlZ1xeQdqUj2t8OVIzZE4=' 'sha256-or0p3LaHetJ4FRq+flVORVFFNsOjQGWrDvX8Jf7ACWg=' 'sha256-jvLh2uL2/Pq/gpvNJMaEL4C+TNhBeGadLIUyPcVRZvY=' - 'sha256-Oca9ZYU1dwNscIhdNV7tFBsr4oqagBhZx9/p4w8GOcg=' + 'sha256-VZTcMoTEw3nbAHejvqlyyRm1Mdx+DVNgyKANjpWw0qg=' ;img-src 'self' data: diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index fd1a3b0b84c..3a78ae0ed01 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -4,7 +4,7 @@ import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { RouterModule } from "@angular/router"; -import { InfiniteScrollModule } from "ngx-infinite-scroll"; +import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CoreModule } from "@bitwarden/web-vault/app/core"; @@ -37,7 +37,7 @@ import { AccessIntelligenceModule } from "./tools/access-intelligence/access-int FormsModule, ReactiveFormsModule, CoreModule, - InfiniteScrollModule, + InfiniteScrollDirective, DragDropModule, AppRoutingModule, OssRoutingModule, diff --git a/package-lock.json b/package-lock.json index ba7953d0faf..c60d71881a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,22 +15,22 @@ "libs/*" ], "dependencies": { - "@angular/animations": "17.3.12", - "@angular/cdk": "17.3.10", - "@angular/common": "17.3.12", - "@angular/compiler": "17.3.12", - "@angular/core": "17.3.12", - "@angular/forms": "17.3.12", - "@angular/platform-browser": "17.3.12", - "@angular/platform-browser-dynamic": "17.3.12", - "@angular/router": "17.3.12", + "@angular/animations": "18.2.13", + "@angular/cdk": "18.2.14", + "@angular/common": "18.2.13", + "@angular/compiler": "18.2.13", + "@angular/core": "18.2.13", + "@angular/forms": "18.2.13", + "@angular/platform-browser": "18.2.13", + "@angular/platform-browser-dynamic": "18.2.13", + "@angular/router": "18.2.13", "@bitwarden/sdk-internal": "0.2.0-main.38", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", - "@ng-select/ng-select": "12.0.7", + "@ng-select/ng-select": "13.9.1", "argon2": "0.41.1", "argon2-browser": "1.18.0", "big-integer": "1.6.52", @@ -53,7 +53,7 @@ "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.1", - "ngx-infinite-scroll": "17.0.1", + "ngx-infinite-scroll": "18.0.0", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1", @@ -74,20 +74,20 @@ "zxcvbn": "4.4.2" }, "devDependencies": { - "@angular-devkit/build-angular": "17.3.11", - "@angular-eslint/eslint-plugin": "17.5.3", - "@angular-eslint/eslint-plugin-template": "17.5.3", - "@angular-eslint/schematics": "17.5.3", - "@angular-eslint/template-parser": "17.5.3", - "@angular/cli": "17.3.11", - "@angular/compiler-cli": "17.3.12", - "@angular/elements": "17.3.12", + "@angular-devkit/build-angular": "18.2.12", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "@angular-eslint/schematics": "18.4.3", + "@angular-eslint/template-parser": "18.4.3", + "@angular/cli": "18.2.12", + "@angular/compiler-cli": "18.2.13", + "@angular/elements": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.1", - "@ngtools/webpack": "17.3.11", + "@ngtools/webpack": "18.2.12", "@storybook/addon-a11y": "8.4.7", "@storybook/addon-actions": "8.4.7", "@storybook/addon-designs": "8.0.4", @@ -369,14 +369,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.11.tgz", - "integrity": "sha512-p+XIc/j51aI83ExNdeZwvkm1F4wkuKMGUUoj0MVUUi5E6NoiMlXYm6uU8+HbRvPBzGy5+3KOiGp3Fks0UmDSAA==", + "version": "0.1802.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", + "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@angular-devkit/core": "18.2.11", + "@angular-devkit/core": "18.2.12", "rxjs": "7.8.1" }, "engines": { @@ -386,98 +384,96 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.11.tgz", - "integrity": "sha512-lHX5V2dSts328yvo/9E2u9QMGcvJhbEKKDDp9dBecwvIG9s+4lTOJgi9DPUE7W+AtmPcmbbhwC2JRQ/SLQhAoA==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.12.tgz", + "integrity": "sha512-quVUi7eqTq9OHumQFNl9Y8t2opm8miu4rlYnuF6rbujmmBDvdUvR6trFChueRczl2p5HWqTOr6NPoDGQm8AyNw==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1703.11", - "@angular-devkit/build-webpack": "0.1703.11", - "@angular-devkit/core": "17.3.11", - "@babel/core": "7.24.0", - "@babel/generator": "7.23.6", - "@babel/helper-annotate-as-pure": "7.22.5", - "@babel/helper-split-export-declaration": "7.22.6", - "@babel/plugin-transform-async-generator-functions": "7.23.9", - "@babel/plugin-transform-async-to-generator": "7.23.3", - "@babel/plugin-transform-runtime": "7.24.0", - "@babel/preset-env": "7.24.0", - "@babel/runtime": "7.24.0", - "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "17.3.11", + "@angular-devkit/architect": "0.1802.12", + "@angular-devkit/build-webpack": "0.1802.12", + "@angular-devkit/core": "18.2.12", + "@angular/build": "18.2.12", + "@babel/core": "7.25.2", + "@babel/generator": "7.25.0", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.25.0", + "@babel/plugin-transform-async-to-generator": "7.24.7", + "@babel/plugin-transform-runtime": "7.24.7", + "@babel/preset-env": "7.25.3", + "@babel/runtime": "7.25.0", + "@discoveryjs/json-ext": "0.6.1", + "@ngtools/webpack": "18.2.12", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", - "autoprefixer": "10.4.18", + "autoprefixer": "10.4.20", "babel-loader": "9.1.3", - "babel-plugin-istanbul": "6.1.1", "browserslist": "^4.21.5", - "copy-webpack-plugin": "11.0.0", - "critters": "0.0.22", - "css-loader": "6.10.0", - "esbuild-wasm": "0.20.1", + "copy-webpack-plugin": "12.0.2", + "critters": "0.0.24", + "css-loader": "7.1.2", + "esbuild-wasm": "0.23.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "2.0.7", - "https-proxy-agent": "7.0.4", - "inquirer": "9.2.15", - "jsonc-parser": "3.2.1", + "http-proxy-middleware": "3.0.3", + "https-proxy-agent": "7.0.5", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", "less": "4.2.0", - "less-loader": "11.1.0", + "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", - "loader-utils": "3.2.1", - "magic-string": "0.30.8", - "mini-css-extract-plugin": "2.8.1", + "loader-utils": "3.3.1", + "magic-string": "0.30.11", + "mini-css-extract-plugin": "2.9.0", "mrmime": "2.0.0", - "open": "8.4.2", + "open": "10.1.0", "ora": "5.4.1", "parse5-html-rewriting-stream": "7.0.0", - "picomatch": "4.0.1", - "piscina": "4.4.0", - "postcss": "8.4.35", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "postcss": "8.4.41", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.71.1", - "sass-loader": "14.1.1", - "semver": "7.6.0", + "sass": "1.77.6", + "sass-loader": "16.0.0", + "semver": "7.6.3", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.29.1", + "terser": "5.31.6", "tree-kill": "1.2.2", - "tslib": "2.6.2", - "undici": "6.11.1", - "vite": "5.1.8", - "watchpack": "2.4.0", + "tslib": "2.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1", "webpack": "5.94.0", - "webpack-dev-middleware": "6.1.2", - "webpack-dev-server": "4.15.1", - "webpack-merge": "5.10.0", + "webpack-dev-middleware": "7.4.2", + "webpack-dev-server": "5.0.4", + "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.20.1" + "esbuild": "0.23.0" }, "peerDependencies": { - "@angular/compiler-cli": "^17.0.0", - "@angular/localize": "^17.0.0", - "@angular/platform-server": "^17.0.0", - "@angular/service-worker": "^17.0.0", + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", "@web/test-runner": "^0.18.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^17.0.0", + "ng-packagr": "^18.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.2 <5.5" + "typescript": ">=5.4 <5.6" }, "peerDependenciesMeta": { "@angular/localize": { @@ -515,87 +511,22 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1703.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.11.tgz", - "integrity": "sha512-YNasVZk4rYdcM6M+KRH8PUBhVyJfqzUYLpO98GgRokW+taIDgifckSlmfDZzQRbw45qiwei1IKCLqcpC8nM5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "17.3.11", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/build-webpack": { - "version": "0.1703.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.11.tgz", - "integrity": "sha512-qbCiiHuoVkD7CtLyWoRi/Vzz6nrEztpF5XIyWUcQu67An1VlxbMTE4yoSQiURjCQMnB/JvS1GPVed7wOq3SJ/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.1703.11", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^4.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", - "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -621,27 +552,28 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/preset-env": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz", - "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.23.3", - "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -653,59 +585,60 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.9", - "@babel/plugin-transform-async-to-generator": "^7.23.3", - "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.4", - "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.4", - "@babel/plugin-transform-classes": "^7.23.8", - "@babel/plugin-transform-computed-properties": "^7.23.3", - "@babel/plugin-transform-destructuring": "^7.23.3", - "@babel/plugin-transform-dotall-regex": "^7.23.3", - "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.4", - "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.4", - "@babel/plugin-transform-for-of": "^7.23.6", - "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.4", - "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", - "@babel/plugin-transform-member-expression-literals": "^7.23.3", - "@babel/plugin-transform-modules-amd": "^7.23.3", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@babel/plugin-transform-modules-systemjs": "^7.23.9", - "@babel/plugin-transform-modules-umd": "^7.23.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", - "@babel/plugin-transform-numeric-separator": "^7.23.4", - "@babel/plugin-transform-object-rest-spread": "^7.24.0", - "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.4", - "@babel/plugin-transform-optional-chaining": "^7.23.4", - "@babel/plugin-transform-parameters": "^7.23.3", - "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.4", - "@babel/plugin-transform-property-literals": "^7.23.3", - "@babel/plugin-transform-regenerator": "^7.23.3", - "@babel/plugin-transform-reserved-words": "^7.23.3", - "@babel/plugin-transform-shorthand-properties": "^7.23.3", - "@babel/plugin-transform-spread": "^7.23.3", - "@babel/plugin-transform-sticky-regex": "^7.23.3", - "@babel/plugin-transform-template-literals": "^7.23.3", - "@babel/plugin-transform-typeof-symbol": "^7.23.3", - "@babel/plugin-transform-unicode-escapes": "^7.23.3", - "@babel/plugin-transform-unicode-property-regex": "^7.23.3", - "@babel/plugin-transform-unicode-regex": "^7.23.3", - "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -720,102 +653,17 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "node_modules/@angular-devkit/build-angular/node_modules/@discoveryjs/json-ext": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", + "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/autoprefixer": { - "version": "10.4.18", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", - "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001591", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=14.17.0" } }, "node_modules/@angular-devkit/build-angular/node_modules/babel-loader": { @@ -836,40 +684,6 @@ "webpack": ">=5" } }, - "node_modules/@angular-devkit/build-angular/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -877,65 +691,16 @@ "dev": true, "license": "MIT" }, - "node_modules/@angular-devkit/build-angular/node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "node_modules/@angular-devkit/build-angular/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, "engines": { - "node": ">= 14.15.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@angular-devkit/build-angular/node_modules/eslint-scope": { @@ -962,100 +727,21 @@ "node": ">=4.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/@angular-devkit/build-angular/node_modules/http-proxy-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", + "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", "dev": true, - "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/inquirer": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", - "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ljharb/through": "^2.3.12", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^3.2.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@angular-devkit/build-angular/node_modules/ipaddr.js": { @@ -1063,11 +749,41 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 10" } }, + "node_modules/@angular-devkit/build-angular/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -1075,12 +791,30 @@ "dev": true, "license": "MIT" }, - "node_modules/@angular-devkit/build-angular/node_modules/mini-css-extract-plugin": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", - "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", + "node_modules/@angular-devkit/build-angular/node_modules/memfs": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.1.tgz", + "integrity": "sha512-Fq5CMEth+2iprLJ5mNizRcWuiwRZYjNkUD0zKk224jZunE9CRacTRDK8QLALbMBlNX2y3nY6lKZbesCwDwacig==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", "dev": true, - "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -1096,47 +830,28 @@ "webpack": "^5.0.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@angular-devkit/build-angular/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" }, "engines": { - "node": "*" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" + "node": ">=18" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@angular-devkit/build-angular/node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "funding": [ { @@ -1152,59 +867,35 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, - "node_modules/@angular-devkit/build-angular/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "dev": true, - "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^10.3.7" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@angular-devkit/build-angular/node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/sass": { - "version": "1.71.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", - "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", "dev": true, - "license": "MIT", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -1218,11 +909,10 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/sass-loader": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", - "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", + "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", "dev": true, - "license": "MIT", "dependencies": { "neo-async": "^2.6.2" }, @@ -1258,17 +948,16 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10" } }, "node_modules/@angular-devkit/build-angular/node_modules/webpack": { @@ -1318,56 +1007,84 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", "dev": true, - "license": "MIT", "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { @@ -1378,28 +1095,28 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, - "license": "MIT", "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=12.0.0" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } } }, "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/ajv": { @@ -1469,13 +1186,30 @@ "node": ">=10.13.0" } }, - "node_modules/@angular-devkit/core": { - "version": "18.2.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.11.tgz", - "integrity": "sha512-H9P1shRGigORWJHUY2BRa2YurT+DVminrhuaYHsbhXBRsPmgB2Dx/30YLTnC1s5XmR9QIRUCsg/d3kyT1wd5Zg==", + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1802.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.12.tgz", + "integrity": "sha512-0Z3fdbZVRnjYWE2/VYyfy+uieY+6YZyEp4ylzklVkc+fmLNsnz4Zw6cK1LzzcBqAwKIyh1IdW20Cg7o8b0sONA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1802.12", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/core": { + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", + "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -1498,219 +1232,706 @@ } } }, - "node_modules/@angular-devkit/core/node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@angular-devkit/core/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular-devkit/schematics": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", - "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.12.tgz", + "integrity": "sha512-mMea9txHbnCX5lXLHlo0RAgfhFHDio45/jMsREM2PA8UtVf2S8ltXz7ZwUrUyMQRv8vaSfn4ijDstF4hDMnRgQ==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.11", - "jsonc-parser": "3.2.1", - "magic-string": "0.30.8", + "@angular-devkit/core": "18.2.12", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.11", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", - "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "17.5.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-17.5.3.tgz", - "integrity": "sha512-x9jZ6mME9wxumErPGonWERXX/9TJ7mzEkQhOKt3BxBFm0sy9XQqLMAenp1PBSg3RF3rH7EEVdB2+jb75RtHp0g==", - "dev": true, - "license": "MIT" + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", + "integrity": "sha512-zdrA8mR98X+U4YgHzUKmivRU+PxzwOL/j8G7eTOvBuq8GPzsP+hvak+tyxlgeGm9HsvpFj9ERHLtJ0xDUPs8fg==", + "dev": true }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "17.5.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-17.5.3.tgz", - "integrity": "sha512-2gMRZ+SkiygrPDtCJwMfjmwIFOcvxxC4NRX/MqRo6udsa0gtqPrc8acRbwrmAXlullmhzmaeUfkHpGDSzW8pFw==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.3.tgz", + "integrity": "sha512-AyJbupiwTBR81P6T59v+aULEnPpZBCBxL2S5QFWfAhNCwWhcof4GihvdK2Z87yhvzDGeAzUFSWl/beJfeFa+PA==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.5.3", - "@angular-eslint/utils": "17.5.3", - "@typescript-eslint/utils": "7.11.0" + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "17.5.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-17.5.3.tgz", - "integrity": "sha512-RkRFagxqBPV2xdNyeQQROUm6I1Izto1Z3Wy73lCk2zq1RhVgbznniH/epmOIE8PMkHmMKmZ765FV++J/90p4Ig==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.3.tgz", + "integrity": "sha512-ijGlX2N01ayMXTpeQivOA31AszO8OEbu9ZQUCxnu9AyMMhxyi2q50bujRChAvN9YXQfdQtbxuajxV6+aiWb5BQ==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.5.3", - "@angular-eslint/utils": "17.5.3", - "@typescript-eslint/type-utils": "7.11.0", - "@typescript-eslint/utils": "7.11.0", - "aria-query": "5.3.0", - "axobject-query": "4.0.0" + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, - "node_modules/@angular-eslint/schematics": { - "version": "17.5.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-17.5.3.tgz", - "integrity": "sha512-a0MlOjNLIM18l/66S+CzhANQR3QH3jDUa1MC50E4KBf1mwjQyfqd6RdfbOTMDjgFlPrfB+5JvoWOHHGj7FFM1A==", + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.3.tgz", + "integrity": "sha512-D5maKn5e6n58+8n7jLFLD4g+RGPOPeDSsvPc1sqial5tEKLxAJQJS9WZ28oef3bhkob6C60D+1H0mMmEEVvyVA==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-eslint/eslint-plugin": "17.5.3", - "@angular-eslint/eslint-plugin-template": "17.5.3", - "ignore": "5.3.1", - "strip-json-comments": "3.1.1", - "tmp": "0.2.3" + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "ignore": "6.0.2", + "semver": "7.6.3", + "strip-json-comments": "3.1.1" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" }, - "peerDependencies": { - "@angular/cli": ">= 17.0.0 < 18.0.0" + "engines": { + "node": ">=10" } }, "node_modules/@angular-eslint/template-parser": { - "version": "17.5.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-17.5.3.tgz", - "integrity": "sha512-NYybOsMkJUtFOW2JWALicipq0kK5+jGwA1MYyRoXjdbDlXltHUb9qkXj7p0fE6uRutBGXDl4288s8g/fZCnAIA==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.3.tgz", + "integrity": "sha512-JZMPtEB8yNip3kg4WDEWQyObSo2Hwf+opq2ElYuwe85GQkGhfJSJ2CQYo4FSwd+c5MUQAqESNRg9QqGYauDsiw==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.5.3", - "eslint-scope": "^8.0.0" + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "eslint-scope": "^8.0.2" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/utils": { - "version": "17.5.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-17.5.3.tgz", - "integrity": "sha512-0nNm1FUOLhVHrdK2PP5dZCYYVmTIkEJ4CmlwpuC4JtCLbD5XAHQpY/ZW5Ff5n1b7KfJt1Zy//jlhkkIaw3LaBQ==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.3.tgz", + "integrity": "sha512-w0bJ9+ELAEiPBSTPPm9bvDngfu1d8JbzUhvs2vU+z7sIz/HMwUZT5S4naypj2kNN0gZYGYrW0lt+HIbW87zTAQ==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "17.5.3", - "@typescript-eslint/utils": "7.11.0" + "@angular-eslint/bundled-angular-compiler": "18.4.3" }, "peerDependencies": { - "eslint": "^7.20.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular/animations": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.3.12.tgz", - "integrity": "sha512-9hsdWF4gRRcVJtPcCcYLaX1CIyM9wUu6r+xRl6zU5hq8qhl35hig6ounz7CXFAzLf0WDBdM16bPHouVGaG76lg==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.13.tgz", + "integrity": "sha512-rG5J5Ek5Hg+Tz2NjkNOaG6PupiNK/lPfophXpsR1t/nWujqnMWX2krahD/i6kgD+jNWNKCJCYSOVvCx/BHOtKA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.12" + "@angular/core": "18.2.13" + } + }, + "node_modules/@angular/build": { + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.12.tgz", + "integrity": "sha512-4Ohz+OSILoL+cCAQ4UTiCT5v6pctu3fXNoNpTEUK46OmxELk9jDITO5rNyNS7TxBn9wY69kjX5VcDf7MenquFQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.12", + "@babel/core": "7.25.2", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-syntax-import-attributes": "7.24.7", + "@inquirer/confirm": "3.1.22", + "@vitejs/plugin-basic-ssl": "1.1.0", + "browserslist": "^4.23.0", + "critters": "0.0.24", + "esbuild": "0.23.0", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.5", + "listr2": "8.2.4", + "lmdb": "3.0.13", + "magic-string": "0.30.11", + "mrmime": "2.0.0", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "rollup": "4.22.4", + "sass": "1.77.6", + "semver": "7.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular/build/node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/build/node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular/build/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@angular/build/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@angular/build/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@angular/build/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@angular/build/node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular/build/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@angular/build/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/@angular/build/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/@angular/build/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular/build/node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@angular/build/node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/@angular/build/node_modules/sass": { + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@angular/build/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/build/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/@angular/build/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular/build/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@angular/build/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/@angular/cdk": { - "version": "17.3.10", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.10.tgz", - "integrity": "sha512-b1qktT2c1TTTe5nTji/kFAVW92fULK0YhYAvJ+BjZTPKu2FniZNe8o4qqQ0pUuvtMu+ZQxp/QqFYoidIVCjScg==", - "license": "MIT", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", + "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -1718,34 +1939,32 @@ "parse5": "^7.1.2" }, "peerDependencies": { - "@angular/common": "^17.0.0 || ^18.0.0", - "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/cli": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.11.tgz", - "integrity": "sha512-8R9LwAGL8hGAWJ4mNG9ZPUrBUzIdmst0Ldua6RJJ+PrqgjX+8IbO+lNnfrOY/XY+Z3LXbCEJflL26f9czCvTPQ==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.12.tgz", + "integrity": "sha512-xhuZ/b7IhqNw1MgXf+arWf4x+GfUSt/IwbdWU4+CO8A7h0Y46zQywouP/KUK3cMQZfVdHdciTBvlpF3vFacA6Q==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1703.11", - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "@schematics/angular": "17.3.11", + "@angular-devkit/architect": "0.1802.12", + "@angular-devkit/core": "18.2.12", + "@angular-devkit/schematics": "18.2.12", + "@inquirer/prompts": "5.3.8", + "@listr2/prompt-adapter-inquirer": "2.0.15", + "@schematics/angular": "18.2.12", "@yarnpkg/lockfile": "1.1.0", - "ansi-colors": "4.1.3", - "ini": "4.1.2", - "inquirer": "9.2.15", - "jsonc-parser": "3.2.1", - "npm-package-arg": "11.0.1", - "npm-pick-manifest": "9.0.0", - "open": "8.4.2", - "ora": "5.4.1", - "pacote": "17.0.6", + "ini": "4.1.3", + "jsonc-parser": "3.3.1", + "listr2": "8.2.4", + "npm-package-arg": "11.0.3", + "npm-pick-manifest": "9.1.0", + "pacote": "18.0.6", "resolve": "1.22.8", - "semver": "7.6.0", + "semver": "7.6.3", "symbol-observable": "4.0.0", "yargs": "17.7.2" }, @@ -1753,189 +1972,196 @@ "ng": "bin/ng.js" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1703.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.11.tgz", - "integrity": "sha512-YNasVZk4rYdcM6M+KRH8PUBhVyJfqzUYLpO98GgRokW+taIDgifckSlmfDZzQRbw45qiwei1IKCLqcpC8nM5Tw==", + "node_modules/@angular/cli/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "17.3.11", - "rxjs": "7.8.1" - }, "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", - "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "node": ">=12" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@angular/cli/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/@angular/cli/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@angular/cli/node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "node_modules/@angular/cli/node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@angular/cli/node_modules/inquirer": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", - "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", - "dev": true, - "license": "MIT", "dependencies": { - "@ljharb/through": "^2.3.12", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^3.2.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@angular/cli/node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "node_modules/@angular/cli/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/@angular/cli/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/@angular/cli/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, - "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@angular/cli/node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "node_modules/@angular/cli/node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", "dev": true, - "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">=18.0.0" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cli/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/@angular/cli/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular/cli/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@angular/cli/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/@angular/common": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.12.tgz", - "integrity": "sha512-vabJzvrx76XXFrm1RJZ6o/CyG32piTB/1sfFfKHdlH1QrmArb8It4gyk9oEjZ1IkAD0HvBWlfWmn+T6Vx3pdUw==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.13.tgz", + "integrity": "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.12", + "@angular/core": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.12.tgz", - "integrity": "sha512-vwI8oOL/gM+wPnptOVeBbMfZYwzRxQsovojZf+Zol9szl0k3SZ3FycWlxxXZGFu3VIEfrP6pXplTmyODS/Lt1w==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.13.tgz", + "integrity": "sha512-TzWcrkopyjFF+WeDr2cRe8CcHjU72KfYV3Sm2TkBkcXrkYX5sDjGWrBGrG3hRB4e4okqchrOCvm1MiTdy2vKMA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.12" + "@angular/core": "18.2.13" }, "peerDependenciesMeta": { "@angular/core": { @@ -1944,15 +2170,14 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.12.tgz", - "integrity": "sha512-1F8M7nWfChzurb7obbvuE7mJXlHtY1UG58pcwcomVtpPb+kPavgAO8OEvJHYBMV+bzSxkXt5UIwL9lt9jHUxZA==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.13.tgz", + "integrity": "sha512-DBSh4AQwkiJDSiVvJATRmjxf6wyUs9pwQLgaFdSlfuTRO+sdb0J2z1r3BYm8t0IqdoyXzdZq2YCH43EmyvD71g==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/core": "7.23.9", + "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^3.0.0", + "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", "reflect-metadata": "^0.2.0", "semver": "^7.0.0", @@ -1965,30 +2190,29 @@ "ngcc": "bundles/ngcc/index.js" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "17.3.12", - "typescript": ">=5.2 <5.5" + "@angular/compiler": "18.2.13", + "typescript": ">=5.4 <5.6" } }, "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -2007,85 +2231,107 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } }, + "node_modules/@angular/compiler-cli/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/compiler-cli/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/core": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.12.tgz", - "integrity": "sha512-MuFt5yKi161JmauUta4Dh0m8ofwoq6Ino+KoOtkYMBGsSx+A7dSm+DUxxNwdj7+DNyg3LjVGCFgBFnq4g8z06A==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.13.tgz", + "integrity": "sha512-8mbWHMgO95OuFV1Ejy4oKmbe9NOJ3WazQf/f7wks8Bck7pcihd0IKhlPBNjFllbF5o+04EYSwFhEtvEgjMDClA==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.14.0" + "zone.js": "~0.14.10" } }, "node_modules/@angular/elements": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-17.3.12.tgz", - "integrity": "sha512-rUfEaV+Ol0bxtcEfNuf/7aVe+3/hAVJMNF/DHG71BSekCxPSH5WR6wE0zsXmVoTBadj+TUDlsyju9o9n3+C5Vg==", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-18.2.13.tgz", + "integrity": "sha512-yahRkXWgFolpWMeVsTIlWYwoq4Bsz6wrfS4b+TKHIZbTCyRUlJ5zBFecpYMwgmVuQDJZp+WkUWZV2Qg7kLJR5w==", "dev": true, - "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "17.3.12", + "@angular/core": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/forms": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.12.tgz", - "integrity": "sha512-tV6r12Q3yEUlXwpVko4E+XscunTIpPkLbaiDn/MTL3Vxi2LZnsLgHyd/i38HaHN+e/H3B0a1ToSOhV5wf3ay4Q==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", + "integrity": "sha512-A67D867fu3DSBhdLWWZl/F5pr7v2+dRM2u3U7ZJ0ewh4a+sv+0yqWdJW+a8xIoiHxS+btGEJL2qAKJiH+MCFfg==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.12", - "@angular/core": "17.3.12", - "@angular/platform-browser": "17.3.12", + "@angular/common": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/platform-browser": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.12.tgz", - "integrity": "sha512-DYY04ptWh/ulMHzd+y52WCE8QnEYGeIiW3hEIFjCN8z0kbIdFdUtEB0IK5vjNL3ejyhUmphcpeT5PYf3YXtqWQ==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.13.tgz", + "integrity": "sha512-tu7ZzY6qD3ATdWFzcTcsAKe7M6cJeWbT/4/bF9unyGO3XBPcNYDKoiz10+7ap2PUd0fmPwvuvTvSNJiFEBnB8Q==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "17.3.12", - "@angular/common": "17.3.12", - "@angular/core": "17.3.12" + "@angular/animations": "18.2.13", + "@angular/common": "18.2.13", + "@angular/core": "18.2.13" }, "peerDependenciesMeta": { "@angular/animations": { @@ -2094,38 +2340,36 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.12.tgz", - "integrity": "sha512-DQwV7B2x/DRLRDSisngZRdLqHdYbbrqZv2Hmu4ZbnNYaWPC8qvzgE/0CvY+UkDat3nCcsfwsMnlDeB6TL7/IaA==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.13.tgz", + "integrity": "sha512-kbQCf9+8EpuJC7buBxhSiwBtXvjAwAKh6MznD6zd2pyCYqfY6gfRCZQRtK59IfgVtKmEONWI9grEyNIRoTmqJg==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.12", - "@angular/compiler": "17.3.12", - "@angular/core": "17.3.12", - "@angular/platform-browser": "17.3.12" + "@angular/common": "18.2.13", + "@angular/compiler": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13" } }, "node_modules/@angular/router": { - "version": "17.3.12", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.12.tgz", - "integrity": "sha512-dg7PHBSW9fmPKTVzwvHEeHZPZdpnUqW/U7kj8D29HTP9ur8zZnx9QcnbplwPeYb8yYa62JMnZSEel2X4PxdYBg==", - "license": "MIT", + "version": "18.2.13", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.13.tgz", + "integrity": "sha512-VKmfgi/r/CkyBq9nChQ/ptmfu0JT/8ONnLVJ5H+SkFLRYJcIRyHLKjRihMCyVm6xM5yktOdCaW73NTQrFz7+bg==", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^18.13.0 || >=20.9.0" + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "17.3.12", - "@angular/core": "17.3.12", - "@angular/platform-browser": "17.3.12", + "@angular/common": "18.2.13", + "@angular/core": "18.2.13", + "@angular/platform-browser": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -2226,15 +2470,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -2242,13 +2485,12 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -2428,19 +2670,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", @@ -2584,13 +2813,12 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -3097,16 +3325,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", - "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -3116,15 +3343,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", - "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -3781,17 +4007,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz", - "integrity": "sha512-zc0GA5IitLKJrSfXlXmp8KDqLrnGECK7YRfQBmEKg1NmBOQ7e+KuclBEKJgzifQeUYLdNiAw4B4bjyvzWVLiSA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0", - "babel-plugin-polyfill-corejs2": "^0.4.8", - "babel-plugin-polyfill-corejs3": "^0.9.0", - "babel-plugin-polyfill-regenerator": "^0.5.5", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "engines": { @@ -3806,7 +4031,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -4091,33 +4315,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4144,11 +4341,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dev": true, - "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -4696,33 +4892,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", - "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@compodoc/compodoc/node_modules/cheerio": { "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", @@ -4816,26 +4985,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@compodoc/compodoc/node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "node_modules/@compodoc/compodoc/node_modules/jackspeak": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", @@ -4865,13 +5014,6 @@ "node": ">=6" } }, - "node_modules/@compodoc/compodoc/node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@compodoc/compodoc/node_modules/lru-cache": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", @@ -4882,16 +5024,6 @@ "node": "20 || >=22" } }, - "node_modules/@compodoc/compodoc/node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, "node_modules/@compodoc/compodoc/node_modules/minimatch": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", @@ -4925,19 +5057,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@compodoc/compodoc/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@compodoc/compodoc/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -5095,16 +5214,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@electron/asar": { "version": "3.2.15", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.15.tgz", @@ -5542,20 +5651,19 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", - "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -5834,6 +5942,252 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@inquirer/checkbox": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", + "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@inquirer/editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", + "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", + "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", + "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", + "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", + "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", + "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", + "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", + "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", + "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -5943,6 +6297,7 @@ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -5960,6 +6315,7 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -5974,6 +6330,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -5987,6 +6344,7 @@ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -6003,6 +6361,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -6586,6 +6945,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", + "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "dev": true, + "dependencies": { + "@inquirer/type": "^1.5.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 6" + } + }, "node_modules/@lit-labs/react": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.2.1.tgz", @@ -6610,18 +6984,83 @@ "@lit-labs/ssr-dom-shim": "^1.0.0" } }, - "node_modules/@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", + "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - } + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", + "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", + "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", + "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", + "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", + "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", @@ -6764,6 +7203,84 @@ "node": ">= 10" } }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@napi-rs/cli": { "version": "2.18.4", "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.4.tgz", @@ -6782,37 +7299,35 @@ } }, "node_modules/@ng-select/ng-select": { - "version": "12.0.7", - "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-12.0.7.tgz", - "integrity": "sha512-Eht1zlLP0DJxiXcKnq3aY/EJ8odomgU0hM0BJoPY6oX3XFHndtFtdPxlZfhVtQn+FwyDEh7306rRx6digxVssA==", - "license": "MIT", + "version": "13.9.1", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-13.9.1.tgz", + "integrity": "sha512-+DzQkQp8coGWZREflJM/qx7BXipV6HEVpZCXoa6fJJRHJfmUMsxa5uV6kUVmClUE98Rkffk9CPHt6kZcj8PuqQ==", "dependencies": { "tslib": "^2.3.1" }, "engines": { - "node": ">= 16", + "node": ">= 18", "npm": ">= 8" }, "peerDependencies": { - "@angular/common": "^17.0.0-rc.0", - "@angular/core": "^17.0.0-rc.0", - "@angular/forms": "^17.0.0-rc.0" + "@angular/common": "^18.0.0", + "@angular/core": "^18.0.0", + "@angular/forms": "^18.0.0" } }, "node_modules/@ngtools/webpack": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.11.tgz", - "integrity": "sha512-SfTCbplt4y6ak5cf2IfqdoVOsnoNdh/j6Vu+wb8WWABKwZ5yfr2S/Gk6ithSKcdIZhAF8DNBOoyk1EJuf8Xkfg==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.12.tgz", + "integrity": "sha512-FFJAwtWbtpncMOVNuULPBwFJB7GSjiUwO93eGTzRp8O4EPQ8lCQeFbezQm/NP34+T0+GBLGzPSuQT+muob8YKw==", "dev": true, - "license": "MIT", "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^17.0.0", - "typescript": ">=5.2 <5.5", + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.6", "webpack": "^5.54.0" } }, @@ -6859,7 +7374,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", "dev": true, - "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", @@ -6876,7 +7390,6 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.3.4" }, @@ -6889,7 +7402,6 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -6902,15 +7414,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@npmcli/agent/node_modules/socks-proxy-agent": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", @@ -6939,7 +7449,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/promise-spawn": "^7.0.0", "ini": "^4.1.3", @@ -6955,22 +7464,11 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/git/node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/@npmcli/git/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=16" } @@ -6979,15 +7477,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@npmcli/git/node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -6997,7 +7493,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -7013,7 +7508,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", "dev": true, - "license": "ISC", "dependencies": { "npm-bundled": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" @@ -7108,7 +7602,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -7118,7 +7611,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/git": "^5.0.0", "glob": "^10.2.2", @@ -7137,7 +7629,6 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, @@ -7149,15 +7640,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@npmcli/package-json/node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -7167,7 +7656,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", "dev": true, - "license": "ISC", "dependencies": { "which": "^4.0.0" }, @@ -7180,7 +7668,6 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=16" } @@ -7190,7 +7677,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -7202,26 +7688,25 @@ } }, "node_modules/@npmcli/redact": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", - "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", "dev": true, - "license": "ISC", "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@npmcli/run-script": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", - "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^5.0.0", "@npmcli/promise-spawn": "^7.0.0", "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", "which": "^4.0.0" }, "engines": { @@ -7233,17 +7718,24 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=16" } }, + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/@npmcli/run-script/node_modules/which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -7369,85 +7861,21 @@ ] }, "node_modules/@schematics/angular": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.11.tgz", - "integrity": "sha512-tvJpTgYC+hCnTyLszYRUZVyNTpPd+C44gh5CPTcG3qkqStzXQwynQAf6X/DjtwXbUiPQF0XfF0+0R489GpdZPA==", + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.12.tgz", + "integrity": "sha512-sIoeipsisK5eTLW3XuNZYcal83AfslBbgI7LnV+3VrXwpasKPGHwo2ZdwhCd2IXAkuJ02Iyu7MyV0aQRM9i/3g==", "dev": true, - "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "jsonc-parser": "3.2.1" + "@angular-devkit/core": "18.2.12", + "@angular-devkit/schematics": "18.2.12", + "jsonc-parser": "3.3.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", - "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@schematics/angular/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -7477,7 +7905,6 @@ "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.3.2" }, @@ -7490,7 +7917,6 @@ "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^16.14.0 || >=18.0.0" } @@ -7500,7 +7926,6 @@ "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^16.14.0 || >=18.0.0" } @@ -7510,7 +7935,6 @@ "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", @@ -7528,7 +7952,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, - "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -7541,7 +7964,6 @@ "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -7565,7 +7987,6 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -7577,15 +7998,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", @@ -7609,7 +8028,6 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -7622,7 +8040,6 @@ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, - "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -7640,7 +8057,6 @@ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -7650,7 +8066,6 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -7663,7 +8078,6 @@ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, - "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -7676,7 +8090,6 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, - "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -7689,7 +8102,6 @@ "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.3.2", "tuf-js": "^2.2.1" @@ -7703,7 +8115,6 @@ "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.1.0", @@ -8615,7 +9026,6 @@ "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, - "license": "MIT", "engines": { "node": "^16.14.0 || >=18.0.0" } @@ -8625,7 +9035,6 @@ "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", "dev": true, - "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.4" @@ -9207,6 +9616,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.10.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", @@ -9485,6 +9903,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.13", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", @@ -9811,95 +10235,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz", - "integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.11.0", - "@typescript-eslint/utils": "7.11.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz", - "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz", - "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz", - "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.11.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/types": { "version": "7.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", @@ -9949,6 +10284,7 @@ "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.11.0", @@ -9972,6 +10308,7 @@ "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/types": "7.11.0", "@typescript-eslint/visitor-keys": "7.11.0" @@ -9990,6 +10327,7 @@ "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^18.18.0 || >=20.0.0" }, @@ -10004,6 +10342,7 @@ "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/types": "7.11.0", "@typescript-eslint/visitor-keys": "7.11.0", @@ -10033,6 +10372,7 @@ "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/types": "7.11.0", "eslint-visitor-keys": "^3.4.3" @@ -10525,19 +10865,6 @@ "node": ">=10" } }, - "node_modules/@yao-pkg/pkg/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -11201,6 +11528,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -11549,13 +11877,12 @@ } }, "node_modules/axobject-query": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", - "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" + "engines": { + "node": ">= 0.4" } }, "node_modules/babel-jest": { @@ -11605,6 +11932,7 @@ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -11659,61 +11987,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", - "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0", - "core-js-compat": "^3.34.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", - "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.5.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", - "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -12640,6 +12932,7 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -13877,11 +14170,11 @@ "license": "MIT" }, "node_modules/critters": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", - "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", + "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", + "deprecated": "Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties", "dev": true, - "license": "Apache-2.0", "dependencies": { "chalk": "^4.1.0", "css-select": "^5.1.0", @@ -13892,26 +14185,6 @@ "postcss-media-query-parser": "^0.2.3" } }, - "node_modules/critters/node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -15454,42 +15727,42 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", - "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", "dev": true, "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.1", - "@esbuild/android-arm": "0.20.1", - "@esbuild/android-arm64": "0.20.1", - "@esbuild/android-x64": "0.20.1", - "@esbuild/darwin-arm64": "0.20.1", - "@esbuild/darwin-x64": "0.20.1", - "@esbuild/freebsd-arm64": "0.20.1", - "@esbuild/freebsd-x64": "0.20.1", - "@esbuild/linux-arm": "0.20.1", - "@esbuild/linux-arm64": "0.20.1", - "@esbuild/linux-ia32": "0.20.1", - "@esbuild/linux-loong64": "0.20.1", - "@esbuild/linux-mips64el": "0.20.1", - "@esbuild/linux-ppc64": "0.20.1", - "@esbuild/linux-riscv64": "0.20.1", - "@esbuild/linux-s390x": "0.20.1", - "@esbuild/linux-x64": "0.20.1", - "@esbuild/netbsd-x64": "0.20.1", - "@esbuild/openbsd-x64": "0.20.1", - "@esbuild/sunos-x64": "0.20.1", - "@esbuild/win32-arm64": "0.20.1", - "@esbuild/win32-ia32": "0.20.1", - "@esbuild/win32-x64": "0.20.1" + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" } }, "node_modules/esbuild-register": { @@ -15506,16 +15779,15 @@ } }, "node_modules/esbuild-wasm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.20.1.tgz", - "integrity": "sha512-6v/WJubRsjxBbQdz6izgvx7LsVFvVaGmSdwrFHmEzoVgfXL89hkKPoQHsnVI2ngOkcBUQT9kmAM1hVL1k/Av4A==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", + "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", "dev": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/escalade": { @@ -17595,6 +17867,7 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -18200,6 +18473,25 @@ "node": ">=12" } }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", @@ -18570,7 +18862,6 @@ "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, - "license": "ISC", "dependencies": { "minimatch": "^9.0.0" }, @@ -18602,8 +18893,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -18775,11 +19065,10 @@ "license": "ISC" }, "node_modules/ini": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", - "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -19255,6 +19544,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -19456,6 +19754,7 @@ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -19473,6 +19772,7 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -20298,6 +20598,16 @@ "fsevents": "^2.3.2" } }, + "node_modules/jest-haste-map/node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "peer": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/jest-junit": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", @@ -21078,6 +21388,7 @@ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -21205,7 +21516,6 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, - "license": "MIT", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -21268,11 +21578,10 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true, - "license": "MIT" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true }, "node_modules/jsonfile": { "version": "6.1.0", @@ -21302,8 +21611,7 @@ "dev": true, "engines": [ "node >= 0.2.0" - ], - "license": "MIT" + ] }, "node_modules/jsqr": { "version": "1.4.0", @@ -21422,16 +21730,6 @@ "node": ">=6" } }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/koa": { "version": "2.15.3", "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", @@ -21662,24 +21960,29 @@ } }, "node_modules/less-loader": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", - "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", "dev": true, - "license": "MIT", - "dependencies": { - "klona": "^2.0.4" - }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "less": "^3.5.0 || ^4.0.0", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/less/node_modules/make-dir": { @@ -22189,6 +22492,37 @@ "@types/trusted-types": "^2.0.2" } }, + "node_modules/lmdb": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", + "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.10.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.0.13", + "@lmdb/lmdb-darwin-x64": "3.0.13", + "@lmdb/lmdb-linux-arm": "3.0.13", + "@lmdb/lmdb-linux-arm64": "3.0.13", + "@lmdb/lmdb-linux-x64": "3.0.13", + "@lmdb/lmdb-win32-x64": "3.0.13" + } + }, + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -22200,11 +22534,10 @@ } }, "node_modules/loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 12.13.0" } @@ -22652,16 +22985,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -23823,8 +24152,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/minimatch": { "version": "9.0.5", @@ -23965,37 +24293,6 @@ "dev": true, "license": "ISC" }, - "node_modules/minipass-json-stream": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.2.tgz", - "integrity": "sha512-myxeeTm57lYs8pH2nxPzmEEg8DGIgW+9mv6D4JZD2pa81I/OBjeU7PtICXV6c9eRGTA5JMDsuIPUZRCyBMYNhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-json-stream/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -24189,6 +24486,37 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/msgpackr": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, "node_modules/multer": { "version": "1.4.5-lts.1", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", @@ -24354,16 +24682,15 @@ } }, "node_modules/ngx-infinite-scroll": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-17.0.1.tgz", - "integrity": "sha512-T+XseajbmT9YTMmPnFV/AfSlwjaV9m2gZtbIIZH3S+yg/rvvfbgkThqs54UWIu+pqcqNR4UhrXfw6mUjCVZD2A==", - "license": "MIT", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-18.0.0.tgz", + "integrity": "sha512-D183TDwpsd9Zl56UmItsl3RzHdN25srAISfg6lc3A8mEKkEgOq0s7ZzRAYcx8DHsAkMgtZqjIPEvMifD3DOB/g==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/common": ">=17.0.0 <18.0.0", - "@angular/core": ">=17.0.0 <18.0.0" + "@angular/common": ">=18.0.0 <19.0.0", + "@angular/core": ">=18.0.0 <19.0.0" } }, "node_modules/ngx-toastr": { @@ -24386,7 +24713,6 @@ "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "!win32" @@ -24401,7 +24727,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true, - "license": "MIT", "optional": true }, "node_modules/no-case": { @@ -24510,7 +24835,6 @@ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", "dev": true, - "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", @@ -24541,12 +24865,25 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/@npmcli/fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, - "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -24559,7 +24896,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -24569,7 +24905,6 @@ "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -24593,7 +24928,6 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -24606,7 +24940,6 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "ISC", "engines": { "node": ">=16" } @@ -24615,15 +24948,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/node-gyp/node_modules/make-fetch-happen": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", @@ -24647,7 +24978,6 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -24660,7 +24990,6 @@ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, - "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -24678,7 +25007,6 @@ "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, - "license": "ISC", "dependencies": { "abbrev": "^2.0.0" }, @@ -24694,7 +25022,6 @@ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -24704,7 +25031,6 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -24717,7 +25043,6 @@ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, - "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -24730,7 +25055,6 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, - "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -24743,7 +25067,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -24810,7 +25133,6 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", @@ -24825,7 +25147,6 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, @@ -24837,8 +25158,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -24878,7 +25198,6 @@ "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", "dev": true, - "license": "ISC", "dependencies": { "npm-normalize-package-bin": "^3.0.0" }, @@ -24891,7 +25210,6 @@ "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, @@ -24904,20 +25222,18 @@ "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm-package-arg": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", - "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, - "license": "ISC", "dependencies": { "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, @@ -24930,7 +25246,6 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, @@ -24942,15 +25257,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/npm-package-arg/node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -24960,7 +25273,6 @@ "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, - "license": "ISC", "dependencies": { "ignore-walk": "^6.0.4" }, @@ -24969,11 +25281,10 @@ } }, "node_modules/npm-pick-manifest": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", - "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", "dev": true, - "license": "ISC", "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", @@ -24985,17 +25296,16 @@ } }, "node_modules/npm-registry-fetch": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", - "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", "dev": true, - "license": "ISC", "dependencies": { - "@npmcli/redact": "^1.1.0", + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", "make-fetch-happen": "^13.0.0", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", "minizlib": "^2.1.2", "npm-package-arg": "^11.0.0", "proc-log": "^4.0.0" @@ -25009,7 +25319,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, - "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -25022,7 +25331,6 @@ "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -25046,7 +25354,6 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -25058,15 +25365,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", @@ -25090,7 +25395,6 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -25103,7 +25407,6 @@ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, - "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -25121,7 +25424,6 @@ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -25131,7 +25433,6 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -25144,7 +25445,6 @@ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, - "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -25157,7 +25457,6 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, - "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -25456,6 +25755,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ordered-binary": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", + "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", + "dev": true + }, "node_modules/os-name": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", @@ -25603,33 +25908,31 @@ "license": "BlueOak-1.0.0" }, "node_modules/pacote": { - "version": "17.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.6.tgz", - "integrity": "sha512-cJKrW21VRE8vVTRskJo78c/RCvwJCn1f4qgfxL4w77SOWrTCRcmfkYHlHtS0gqpgjv3zhXflRtgsrUCX5xwNnQ==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", + "@npmcli/run-script": "^8.0.0", "cacache": "^18.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^11.0.0", "npm-packlist": "^8.0.0", "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, "bin": { - "pacote": "lib/bin.js" + "pacote": "bin/index.js" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -25640,7 +25943,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, - "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -25653,7 +25955,6 @@ "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -25677,7 +25978,6 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -25689,15 +25989,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/pacote/node_modules/minipass-collect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -25706,11 +26004,10 @@ } }, "node_modules/pacote/node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -25720,7 +26017,6 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -25733,7 +26029,6 @@ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, - "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -25746,7 +26041,6 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, - "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -26157,11 +26451,10 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -26202,11 +26495,10 @@ } }, "node_modules/piscina": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.4.0.tgz", - "integrity": "sha512-+AQduEJefrOApE4bV7KRmp3N2JnnyErlVqq4P/jmko4FPz9Z877BCccl/iB3FdrWSUkvbGV9Kan/KllJgat3Vg==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", + "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", "dev": true, - "license": "MIT", "optionalDependencies": { "nice-napi": "^1.0.2" } @@ -26582,8 +26874,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", @@ -27318,37 +27609,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/read-package-json": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz", - "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==", - "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", - "dev": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -28947,7 +29207,6 @@ "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", @@ -29183,7 +29442,6 @@ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -29193,15 +29451,13 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" + "dev": true }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, - "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -29211,8 +29467,7 @@ "version": "3.0.20", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", - "dev": true, - "license": "CC0-1.0" + "dev": true }, "node_modules/spdy": { "version": "4.0.2", @@ -29264,7 +29519,8 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/ssri": { "version": "9.0.1", @@ -29975,11 +30231,10 @@ } }, "node_modules/terser": { - "version": "5.29.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", - "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -30125,6 +30380,7 @@ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -30140,6 +30396,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -30152,6 +30409,7 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -30173,6 +30431,7 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -30264,19 +30523,6 @@ "node": ">=12.0.0" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", @@ -30652,10 +30898,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "license": "0BSD" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -30713,7 +30958,6 @@ "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", "dev": true, - "license": "MIT", "dependencies": { "@tufjs/models": "2.0.1", "debug": "^4.3.4", @@ -30728,7 +30972,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, - "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -30741,7 +30984,6 @@ "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -30765,7 +31007,6 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -30777,15 +31018,13 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/tuf-js/node_modules/make-fetch-happen": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", "dev": true, - "license": "ISC", "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", @@ -30809,7 +31048,6 @@ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -30822,7 +31060,6 @@ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, - "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -30840,7 +31077,6 @@ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -30850,7 +31086,6 @@ "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, - "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, @@ -30863,7 +31098,6 @@ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, - "license": "ISC", "dependencies": { "unique-slug": "^4.0.0" }, @@ -30876,7 +31110,6 @@ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, - "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, @@ -31202,16 +31435,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz", - "integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0" - } - }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -31604,7 +31827,6 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -31615,7 +31837,6 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, - "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -31691,15 +31912,14 @@ } }, "node_modules/vite": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.8.tgz", - "integrity": "sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, - "license": "MIT", "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -31718,6 +31938,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -31735,6 +31956,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -31747,14 +31971,13 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -31764,12 +31987,11 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -31777,29 +31999,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/w3c-xmlserializer": { @@ -31841,23 +32063,11 @@ "dev": true, "license": "ISC" }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, - "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -31871,7 +32081,6 @@ "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, - "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -31880,16 +32089,20 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "license": "MIT", "dependencies": { "defaults": "^1.0.3" } }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", "engines": { "node": ">=12" } @@ -31899,7 +32112,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dev": true, - "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -31946,7 +32158,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, - "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -31999,32 +32210,15 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" } }, - "node_modules/webpack-cli/node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/webpack-dev-middleware": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", - "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.3.tgz", + "integrity": "sha512-A4ChP0Qj8oGociTs6UdlRUGANIGrCDL3y+pmQMc+dSsraXHCatFpmMey4mYELA+juqwUqwQsUgJJISXl1KWmiw==", "dev": true, - "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.12", @@ -32053,7 +32247,6 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.0.tgz", "integrity": "sha512-90SqqYXA2SK36KcT6o1bvwvZfJFcmoamqeJY7+boioffX9g9C0wjjJRGUrQIuh43pb0ttX7+ssavmj/WN2RHtA==", "dev": true, - "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -32110,7 +32303,6 @@ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -32123,7 +32315,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 10" } @@ -32133,7 +32324,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dev": true, - "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" }, @@ -32145,11 +32335,10 @@ } }, "node_modules/webpack-dev-server/node_modules/memfs": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.15.0.tgz", - "integrity": "sha512-q9MmZXd2rRWHS6GU3WEm3HyiXZyyoA1DqdOhEq0lxPBmKb5S7IAOwX0RgUCwJfqjelDCySa5h8ujOy24LqsWcw==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.15.3.tgz", + "integrity": "sha512-vR/g1SgqvKJgAyYla+06G4p/EOcEmwhYuVb1yc1ixcKf8o/sh7Zngv63957ZSNd1xrZJoinmNyDf2LzuP8WJXw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/json-pack": "^1.0.3", "@jsonjoy.com/util": "^1.3.0", @@ -32226,18 +32415,17 @@ } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, - "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-node-externals": { @@ -32294,7 +32482,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -32311,7 +32498,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -32368,7 +32554,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -32384,8 +32569,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", @@ -32826,6 +33010,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-stream": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", diff --git a/package.json b/package.json index 843e9f34bb6..9922b6d7bac 100644 --- a/package.json +++ b/package.json @@ -35,20 +35,20 @@ "libs/*" ], "devDependencies": { - "@angular-devkit/build-angular": "17.3.11", - "@angular-eslint/eslint-plugin": "17.5.3", - "@angular-eslint/eslint-plugin-template": "17.5.3", - "@angular-eslint/schematics": "17.5.3", - "@angular-eslint/template-parser": "17.5.3", - "@angular/cli": "17.3.11", - "@angular/compiler-cli": "17.3.12", - "@angular/elements": "17.3.12", + "@angular-devkit/build-angular": "18.2.12", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "@angular-eslint/schematics": "18.4.3", + "@angular-eslint/template-parser": "18.4.3", + "@angular/cli": "18.2.12", + "@angular/compiler-cli": "18.2.13", + "@angular/elements": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.1", - "@ngtools/webpack": "17.3.11", + "@ngtools/webpack": "18.2.12", "@storybook/addon-a11y": "8.4.7", "@storybook/addon-actions": "8.4.7", "@storybook/addon-designs": "8.0.4", @@ -145,22 +145,22 @@ "webpack-node-externals": "3.0.0" }, "dependencies": { - "@angular/animations": "17.3.12", - "@angular/cdk": "17.3.10", - "@angular/common": "17.3.12", - "@angular/compiler": "17.3.12", - "@angular/core": "17.3.12", - "@angular/forms": "17.3.12", - "@angular/platform-browser": "17.3.12", - "@angular/platform-browser-dynamic": "17.3.12", - "@angular/router": "17.3.12", + "@angular/animations": "18.2.13", + "@angular/cdk": "18.2.14", + "@angular/common": "18.2.13", + "@angular/compiler": "18.2.13", + "@angular/core": "18.2.13", + "@angular/forms": "18.2.13", + "@angular/platform-browser": "18.2.13", + "@angular/platform-browser-dynamic": "18.2.13", + "@angular/router": "18.2.13", "@bitwarden/sdk-internal": "0.2.0-main.38", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", - "@ng-select/ng-select": "12.0.7", + "@ng-select/ng-select": "13.9.1", "argon2": "0.41.1", "argon2-browser": "1.18.0", "big-integer": "1.6.52", @@ -183,7 +183,7 @@ "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.1", - "ngx-infinite-scroll": "17.0.1", + "ngx-infinite-scroll": "18.0.0", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1", From 26f086368b23f57925ecad244d659b5aa0f760dd Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:01:56 -0600 Subject: [PATCH 069/270] fix(auth): [PM-15987] improve email/master password entry back/forward navigation - Fix back button behavior in Safari to reliably return to email entry screen - Enable browser forward button after navigating back to email entry - Move email validation to input event instead of blur - Add continueClicked function to differentiate user clicks vs browser navigation - Add email verification gate to SSO route - Enhance master password validation logic - Fix strict typing errors Resolves PM-15987 --- .../src/angular/login/login.component.html | 37 +---- .../auth/src/angular/login/login.component.ts | 142 ++++++++++++------ 2 files changed, 103 insertions(+), 76 deletions(-) diff --git a/libs/auth/src/angular/login/login.component.html b/libs/auth/src/angular/login/login.component.html index 54a04d3de6c..c7837db74f2 100644 --- a/libs/auth/src/angular/login/login.component.html +++ b/libs/auth/src/angular/login/login.component.html @@ -20,8 +20,8 @@ formControlName="email" bitInput appAutofocus - (blur)="onEmailBlur($event)" - (keyup.enter)="continue()" + (input)="onEmailInput($event)" + (keyup.enter)="continuePressed()" /> @@ -33,7 +33,7 @@
    - @@ -54,33 +54,10 @@ - - - - {{ "useSingleSignOn" | i18n }} - - - - - +
    diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 33c167dcaed..40f85e6d75c 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; @@ -12,7 +10,6 @@ import { LoginStrategyServiceAbstraction, LoginSuccessHandlerService, PasswordLoginCredentials, - RegisterRouteService, } from "@bitwarden/auth/common"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; @@ -72,16 +69,15 @@ export enum LoginUiState { ], }) export class LoginComponent implements OnInit, OnDestroy { - @ViewChild("masterPasswordInputRef") masterPasswordInputRef: ElementRef; + @ViewChild("masterPasswordInputRef") masterPasswordInputRef: ElementRef | undefined; private destroy$ = new Subject(); - private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions = undefined; + private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions | undefined = undefined; readonly Icons = { WaveIcon, VaultIcon }; clientType: ClientType; ClientType = ClientType; LoginUiState = LoginUiState; - registerRoute$ = this.registerRouteService.registerRoute$(); // TODO: remove when email verification flag is removed isKnownDevice = false; loginUiState: LoginUiState = LoginUiState.EMAIL_ENTRY; @@ -97,13 +93,13 @@ export class LoginComponent implements OnInit, OnDestroy { { updateOn: "submit" }, ); - get emailFormControl(): FormControl { + get emailFormControl(): FormControl { return this.formGroup.controls.email; } // Web properties - enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; - policies: Policy[]; + enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions | undefined; + policies: Policy[] | undefined; showResetPasswordAutoEnrollWarning = false; // Desktop properties @@ -125,7 +121,6 @@ export class LoginComponent implements OnInit, OnDestroy { private passwordStrengthService: PasswordStrengthServiceAbstraction, private platformUtilsService: PlatformUtilsService, private policyService: InternalPolicyService, - private registerRouteService: RegisterRouteService, private router: Router, private toastService: ToastService, private logService: LogService, @@ -200,12 +195,12 @@ export class LoginComponent implements OnInit, OnDestroy { return; } - const credentials = new PasswordLoginCredentials( - email, - masterPassword, - null, // captcha no longer used in new login / registration scenarios - null, - ); + if (!email || !masterPassword) { + this.logService.error("Email and master password are required"); + return; + } + + const credentials = new PasswordLoginCredentials(email, masterPassword); try { const authResult = await this.loginStrategyService.logIn(credentials); @@ -301,7 +296,12 @@ export class LoginComponent implements OnInit, OnDestroy { } protected async launchSsoBrowserWindow(clientId: "browser" | "desktop"): Promise { - await this.loginComponentService.launchSsoBrowserWindow(this.emailFormControl.value, clientId); + const email = this.emailFormControl.value; + if (!email) { + this.logService.error("Email is required for SSO login"); + return; + } + await this.loginComponentService.launchSsoBrowserWindow(email, clientId); } protected async evaluatePassword(): Promise { @@ -337,9 +337,14 @@ export class LoginComponent implements OnInit, OnDestroy { const masterPassword = this.formGroup.controls.masterPassword.value; + // Return false if masterPassword is null/undefined since this is only evaluated after successful login + if (!masterPassword) { + return false; + } + const passwordStrength = this.passwordStrengthService.getPasswordStrength( masterPassword, - this.formGroup.value.email, + this.formGroup.value.email ?? undefined, )?.score; return !this.policyService.evaluateMasterPassword( @@ -363,6 +368,7 @@ export class LoginComponent implements OnInit, OnDestroy { protected async validateEmail(): Promise { this.formGroup.controls.email.markAsTouched(); + this.formGroup.controls.email.updateValueAndValidity({ onlySelf: true, emitEvent: true }); return this.formGroup.controls.email.valid; } @@ -404,7 +410,10 @@ export class LoginComponent implements OnInit, OnDestroy { } // Check to see if the device is known so we can show the Login with Device option - await this.getKnownDevice(this.emailFormControl.value); + const email = this.emailFormControl.value; + if (email) { + await this.getKnownDevice(email); + } } } @@ -412,11 +421,10 @@ export class LoginComponent implements OnInit, OnDestroy { * Set the email value from the input field. * @param event The event object from the input field. */ - onEmailBlur(event: Event) { + onEmailInput(event: Event) { const emailInput = event.target as HTMLInputElement; this.formGroup.controls.email.setValue(emailInput.value); - // Call setLoginEmail so that the email is pre-populated when navigating to the "enter password" screen. - this.loginEmailService.setLoginEmail(this.formGroup.value.email); + this.loginEmailService.setLoginEmail(emailInput.value); } isLoginWithPasskeySupported() { @@ -428,28 +436,36 @@ export class LoginComponent implements OnInit, OnDestroy { await this.router.navigateByUrl("/hint"); } - protected async goToRegister(): Promise { - // TODO: remove when email verification flag is removed - const registerRoute = await firstValueFrom(this.registerRoute$); - - if (this.emailFormControl.valid) { - await this.router.navigate([registerRoute], { - queryParams: { email: this.emailFormControl.value }, - }); + protected async saveEmailSettings(): Promise { + const email = this.formGroup.value.email; + if (!email) { + this.logService.error("Email is required to save email settings."); return; } - await this.router.navigate([registerRoute]); - } - - protected async saveEmailSettings(): Promise { - await this.loginEmailService.setLoginEmail(this.formGroup.value.email); - this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail); + await this.loginEmailService.setLoginEmail(email); + this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail ?? false); await this.loginEmailService.saveEmailSettings(); } + /** + * Continue button clicked (or enter key pressed). + * Adds the login url to the browser's history so that the back button can be used to go back to the email entry state. + * Needs to be separate from the continue() function because that can be triggered by the browser's forward button. + */ + protected async continuePressed() { + // Add a new entry to the browser's history so that there is a history entry to go back to + history.pushState({}, "", window.location.href); + await this.continue(); + } + + /** + * Continue to the master password entry state (only if email is validated) + */ protected async continue(): Promise { - if (await this.validateEmail()) { + const isEmailValid = await this.validateEmail(); + + if (isEmailValid) { await this.toggleLoginUiState(LoginUiState.MASTER_PASSWORD_ENTRY); } } @@ -460,6 +476,11 @@ export class LoginComponent implements OnInit, OnDestroy { * @param email - The user's email */ private async getKnownDevice(email: string): Promise { + if (!email) { + this.isKnownDevice = false; + return; + } + try { const deviceIdentifier = await this.appIdService.getAppId(); this.isKnownDevice = await this.devicesApiService.getKnownDevice(email, deviceIdentifier); @@ -503,7 +524,7 @@ export class LoginComponent implements OnInit, OnDestroy { const orgPolicies = await this.loginComponentService.getOrgPolicies(); this.policies = orgPolicies?.policies; - this.showResetPasswordAutoEnrollWarning = orgPolicies?.isPolicyAndAutoEnrollEnabled; + this.showResetPasswordAutoEnrollWarning = orgPolicies?.isPolicyAndAutoEnrollEnabled ?? false; let paramEmailIsSet = false; @@ -525,7 +546,9 @@ export class LoginComponent implements OnInit, OnDestroy { } // Check to see if the device is known so that we can show the Login with Device option - await this.getKnownDevice(this.emailFormControl.value); + if (this.emailFormControl.value) { + await this.getKnownDevice(this.emailFormControl.value); + } // Backup check to handle unknown case where activatedRoute is not available // This shouldn't happen under normal circumstances @@ -573,23 +596,50 @@ export class LoginComponent implements OnInit, OnDestroy { * Handle the back button click to transition back to the email entry state. */ protected async backButtonClicked() { - // Replace the history so the "forward" button doesn't show (which wouldn't do anything) - history.pushState(null, "", window.location.pathname); - await this.toggleLoginUiState(LoginUiState.EMAIL_ENTRY); + history.back(); } /** * Handle the popstate event to transition back to the email entry state when the back button is clicked. + * Also handles the case where the user clicks the forward button. * @param event - The popstate event. */ - private handlePopState = (event: PopStateEvent) => { + private handlePopState = async (event: PopStateEvent) => { if (this.loginUiState === LoginUiState.MASTER_PASSWORD_ENTRY) { - // Prevent default navigation + // Prevent default navigation when the browser's back button is clicked event.preventDefault(); - // Replace the history so the "forward" button doesn't show (which wouldn't do anything) - history.pushState(null, "", window.location.pathname); // Transition back to email entry state void this.toggleLoginUiState(LoginUiState.EMAIL_ENTRY); + } else if (this.loginUiState === LoginUiState.EMAIL_ENTRY) { + // Prevent default navigation when the browser's forward button is clicked + event.preventDefault(); + // Continue to the master password entry state + await this.continue(); } }; + + /** + * Handle the SSO button click. + * @param event - The event object. + */ + async handleSsoClick() { + const isEmailValid = await this.validateEmail(); + + if (!isEmailValid) { + return; + } + + await this.saveEmailSettings(); + + if (this.clientType === ClientType.Web) { + await this.router.navigate(["/sso"], { + queryParams: { email: this.formGroup.value.email }, + }); + return; + } + + await this.launchSsoBrowserWindow( + this.clientType === ClientType.Browser ? "browser" : "desktop", + ); + } } From 6aa5b1b953f30fd3dc54bfd424f5558ea15f7dc9 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:14:16 +0100 Subject: [PATCH 070/270] [PM-7105][PM-7242][PM-16256] Remove v1 code for Tab/Vault Part 2 (#12516) * Remove v1 code for Tab/Vault Part 2 * Removal conditional for assign-collections --------- Co-authored-by: Daniel James Smith --- apps/browser/src/popup/app-routing.module.ts | 89 +- apps/browser/src/popup/app.module.ts | 38 - .../vault/guards/clear-vault-state.guard.ts | 2 +- .../components/action-buttons.component.html | 102 --- .../components/action-buttons.component.ts | 108 --- .../components/cipher-row.component.html | 51 -- .../popup/components/cipher-row.component.ts | 31 - .../vault-v2.component.html | 0 .../{vault => vault-v2}/vault-v2.component.ts | 8 +- .../add-edit-custom-fields.component.html | 140 --- .../vault/add-edit-custom-fields.component.ts | 15 - .../components/vault/add-edit.component.html | 826 ------------------ .../components/vault/add-edit.component.ts | 417 --------- .../vault/attachments.component.html | 72 -- .../components/vault/attachments.component.ts | 82 -- .../vault/collections.component.html | 43 - .../components/vault/collections.component.ts | 61 -- .../vault/current-tab.component.html | 95 -- .../components/vault/current-tab.component.ts | 354 -------- .../vault/password-history.component.html | 40 - .../vault/password-history.component.ts | 44 - .../components/vault/share.component.html | 77 -- .../popup/components/vault/share.component.ts | 72 -- .../vault/vault-filter.component.html | 238 ----- .../vault/vault-filter.component.ts | 482 ---------- .../vault/vault-items.component.html | 123 --- .../components/vault/vault-items.component.ts | 316 ------- .../vault/vault-select.component.html | 82 -- .../vault/vault-select.component.ts | 227 ----- .../vault/view-custom-fields.component.html | 98 --- .../vault/view-custom-fields.component.ts | 14 - .../components/vault/view.component.html | 719 --------------- .../popup/components/vault/view.component.ts | 443 ---------- .../popup/settings/appearance.component.html | 80 -- .../popup/settings/appearance.component.ts | 75 -- .../settings/folder-add-edit.component.html | 49 -- .../settings/folder-add-edit.component.ts | 78 -- .../popup/settings/folders.component.html | 38 - .../vault/popup/settings/folders.component.ts | 48 - .../vault/popup/settings/sync.component.html | 35 - .../vault/popup/settings/sync.component.ts | 46 - .../settings/vault-settings.component.html | 56 -- .../settings/vault-settings.component.ts | 25 - 43 files changed, 31 insertions(+), 6008 deletions(-) delete mode 100644 apps/browser/src/vault/popup/components/action-buttons.component.html delete mode 100644 apps/browser/src/vault/popup/components/action-buttons.component.ts delete mode 100644 apps/browser/src/vault/popup/components/cipher-row.component.html delete mode 100644 apps/browser/src/vault/popup/components/cipher-row.component.ts rename apps/browser/src/vault/popup/components/{vault => vault-v2}/vault-v2.component.html (100%) rename apps/browser/src/vault/popup/components/{vault => vault-v2}/vault-v2.component.ts (95%) delete mode 100644 apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/add-edit.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/add-edit.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/attachments.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/attachments.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/collections.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/collections.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/current-tab.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/current-tab.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/password-history.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/password-history.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/share.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/share.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-filter.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-filter.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-items.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-items.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-select.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-select.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/view-custom-fields.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/view-custom-fields.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/view.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault/view.component.ts delete mode 100644 apps/browser/src/vault/popup/settings/appearance.component.html delete mode 100644 apps/browser/src/vault/popup/settings/appearance.component.ts delete mode 100644 apps/browser/src/vault/popup/settings/folder-add-edit.component.html delete mode 100644 apps/browser/src/vault/popup/settings/folder-add-edit.component.ts delete mode 100644 apps/browser/src/vault/popup/settings/folders.component.html delete mode 100644 apps/browser/src/vault/popup/settings/folders.component.ts delete mode 100644 apps/browser/src/vault/popup/settings/sync.component.html delete mode 100644 apps/browser/src/vault/popup/settings/sync.component.ts delete mode 100644 apps/browser/src/vault/popup/settings/vault-settings.component.html delete mode 100644 apps/browser/src/vault/popup/settings/vault-settings.component.ts diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 49d680cd752..161c4ca0524 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -93,28 +93,16 @@ import { ExportBrowserV2Component } from "../tools/popup/settings/export/export- import { ImportBrowserV2Component } from "../tools/popup/settings/import/import-browser-v2.component"; import { SettingsV2Component } from "../tools/popup/settings/settings-v2.component"; import { clearVaultStateGuard } from "../vault/guards/clear-vault-state.guard"; -import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; -import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; -import { CollectionsComponent } from "../vault/popup/components/vault/collections.component"; -import { PasswordHistoryComponent } from "../vault/popup/components/vault/password-history.component"; -import { ShareComponent } from "../vault/popup/components/vault/share.component"; -import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component"; -import { VaultV2Component } from "../vault/popup/components/vault/vault-v2.component"; -import { ViewComponent } from "../vault/popup/components/vault/view.component"; import { AddEditV2Component } from "../vault/popup/components/vault-v2/add-edit/add-edit-v2.component"; import { AssignCollections } from "../vault/popup/components/vault-v2/assign-collections/assign-collections.component"; import { AttachmentsV2Component } from "../vault/popup/components/vault-v2/attachments/attachments-v2.component"; import { PasswordHistoryV2Component } from "../vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component"; +import { VaultV2Component } from "../vault/popup/components/vault-v2/vault-v2.component"; import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component"; import { AppearanceV2Component } from "../vault/popup/settings/appearance-v2.component"; -import { AppearanceComponent } from "../vault/popup/settings/appearance.component"; -import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component"; -import { FoldersComponent } from "../vault/popup/settings/folders.component"; -import { SyncComponent } from "../vault/popup/settings/sync.component"; import { TrashComponent } from "../vault/popup/settings/trash.component"; import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component"; -import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component"; import { RouteElevation } from "./app-routing.animations"; import { debounceNavigationGuard } from "./services/debounce-navigation.service"; @@ -271,56 +259,43 @@ const routes: Routes = [ data: { elevation: 1 } satisfies RouteDataProperties, }, { - path: "ciphers", - component: VaultItemsComponent, - canActivate: [authGuard], - data: { elevation: 1 } satisfies RouteDataProperties, - }, - ...extensionRefreshSwap(ViewComponent, ViewV2Component, { path: "view-cipher", + component: ViewV2Component, canActivate: [authGuard], data: { // Above "trash" elevation: 3, } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(PasswordHistoryComponent, PasswordHistoryV2Component, { + }, + { path: "cipher-password-history", + component: PasswordHistoryV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { + }, + { path: "add-cipher", + component: AddEditV2Component, canActivate: [authGuard, debounceNavigationGuard()], data: { elevation: 1 } satisfies RouteDataProperties, runGuardsAndResolvers: "always", - }), - ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { + }, + { path: "edit-cipher", + component: AddEditV2Component, canActivate: [authGuard, debounceNavigationGuard()], data: { // Above "trash" elevation: 3, } satisfies RouteDataProperties, runGuardsAndResolvers: "always", - }), - { - path: "share-cipher", - component: ShareComponent, - canActivate: [authGuard], - data: { elevation: 1 } satisfies RouteDataProperties, }, { - path: "collections", - component: CollectionsComponent, - canActivate: [authGuard], - data: { elevation: 1 } satisfies RouteDataProperties, - }, - ...extensionRefreshSwap(AttachmentsComponent, AttachmentsV2Component, { path: "attachments", + component: AttachmentsV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "generator", component: CredentialGeneratorComponent, @@ -361,33 +336,17 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }), - ...extensionRefreshSwap(VaultSettingsComponent, VaultSettingsV2Component, { + { path: "vault-settings", + component: VaultSettingsV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(FoldersComponent, FoldersV2Component, { + }, + { path: "folders", + component: FoldersV2Component, canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, - }), - { - path: "add-folder", - component: FolderAddEditComponent, - canActivate: [authGuard], - data: { elevation: 1 } satisfies RouteDataProperties, - }, - { - path: "edit-folder", - component: FolderAddEditComponent, - canActivate: [authGuard], - data: { elevation: 1 } satisfies RouteDataProperties, - }, - { - path: "sync", - component: SyncComponent, - canActivate: [authGuard], - data: { elevation: 1 } satisfies RouteDataProperties, }, ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { path: "excluded-domains", @@ -400,16 +359,18 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(AppearanceComponent, AppearanceV2Component, { + { path: "appearance", + component: AppearanceV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(AddEditComponent, AddEditV2Component, { + }, + { path: "clone-cipher", + component: AddEditV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "add-send", component: SendAddEditV2Component, @@ -685,7 +646,7 @@ const routes: Routes = [ { path: "assign-collections", component: AssignCollections, - canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh, true, "/")], + canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, { diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 83475a661c9..1fe8f1f18db 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -54,25 +54,6 @@ import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.comp import { PopupPageComponent } from "../platform/popup/layout/popup-page.component"; import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component"; import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component"; -import { ActionButtonsComponent } from "../vault/popup/components/action-buttons.component"; -import { CipherRowComponent } from "../vault/popup/components/cipher-row.component"; -import { AddEditCustomFieldsComponent } from "../vault/popup/components/vault/add-edit-custom-fields.component"; -import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; -import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; -import { CollectionsComponent } from "../vault/popup/components/vault/collections.component"; -import { CurrentTabComponent } from "../vault/popup/components/vault/current-tab.component"; -import { PasswordHistoryComponent } from "../vault/popup/components/vault/password-history.component"; -import { ShareComponent } from "../vault/popup/components/vault/share.component"; -import { VaultFilterComponent } from "../vault/popup/components/vault/vault-filter.component"; -import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component"; -import { VaultSelectComponent } from "../vault/popup/components/vault/vault-select.component"; -import { ViewCustomFieldsComponent } from "../vault/popup/components/vault/view-custom-fields.component"; -import { ViewComponent } from "../vault/popup/components/vault/view.component"; -import { AppearanceComponent } from "../vault/popup/settings/appearance.component"; -import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; -import { FoldersComponent } from "../vault/popup/settings/folders.component"; -import { SyncComponent } from "../vault/popup/settings/sync.component"; -import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; @@ -127,48 +108,29 @@ import "../platform/popup/locales"; ExtensionAnonLayoutWrapperComponent, ], declarations: [ - ActionButtonsComponent, - AddEditComponent, - AddEditCustomFieldsComponent, AppComponent, - AttachmentsComponent, - CipherRowComponent, - VaultItemsComponent, - CollectionsComponent, ColorPasswordPipe, ColorPasswordCountPipe, - CurrentTabComponent, EnvironmentComponent, ExcludedDomainsV1Component, Fido2CipherRowV1Component, Fido2UseBrowserLinkV1Component, - FolderAddEditComponent, - FoldersComponent, - VaultFilterComponent, HintComponent, HomeComponent, LoginViaAuthRequestComponentV1, LoginComponentV1, LoginDecryptionOptionsComponentV1, NotificationsSettingsV1Component, - AppearanceComponent, - PasswordHistoryComponent, RegisterComponent, SetPasswordComponent, - VaultSettingsComponent, - ShareComponent, SsoComponentV1, - SyncComponent, TabsV2Component, TwoFactorComponent, TwoFactorOptionsComponent, UpdateTempPasswordComponent, UserVerificationComponent, VaultTimeoutInputComponent, - ViewComponent, - ViewCustomFieldsComponent, RemovePasswordComponent, - VaultSelectComponent, Fido2V1Component, AutofillV1Component, EnvironmentSelectorComponent, diff --git a/apps/browser/src/vault/guards/clear-vault-state.guard.ts b/apps/browser/src/vault/guards/clear-vault-state.guard.ts index 2b43f1ecbd3..b212c55d833 100644 --- a/apps/browser/src/vault/guards/clear-vault-state.guard.ts +++ b/apps/browser/src/vault/guards/clear-vault-state.guard.ts @@ -1,7 +1,7 @@ import { inject } from "@angular/core"; import { CanDeactivateFn } from "@angular/router"; -import { VaultV2Component } from "../popup/components/vault/vault-v2.component"; +import { VaultV2Component } from "../popup/components/vault-v2/vault-v2.component"; import { VaultPopupItemsService } from "../popup/services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../popup/services/vault-popup-list-filters.service"; diff --git a/apps/browser/src/vault/popup/components/action-buttons.component.html b/apps/browser/src/vault/popup/components/action-buttons.component.html deleted file mode 100644 index f63c1f1ac32..00000000000 --- a/apps/browser/src/vault/popup/components/action-buttons.component.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - diff --git a/apps/browser/src/vault/popup/components/action-buttons.component.ts b/apps/browser/src/vault/popup/components/action-buttons.component.ts deleted file mode 100644 index fd559aca817..00000000000 --- a/apps/browser/src/vault/popup/components/action-buttons.component.ts +++ /dev/null @@ -1,108 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { Subject, takeUntil } from "rxjs"; - -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EventType } from "@bitwarden/common/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -@Component({ - selector: "app-action-buttons", - templateUrl: "action-buttons.component.html", -}) -export class ActionButtonsComponent implements OnInit, OnDestroy { - @Output() onView = new EventEmitter(); - @Output() launchEvent = new EventEmitter(); - @Input() cipher: CipherView; - @Input() showView = false; - - cipherType = CipherType; - userHasPremiumAccess = false; - - private componentIsDestroyed$ = new Subject(); - - constructor( - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private eventCollectionService: EventCollectionService, - private totpService: TotpServiceAbstraction, - private passwordRepromptService: PasswordRepromptService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - ) {} - - ngOnInit() { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.componentIsDestroyed$)) - .subscribe((canAccessPremium: boolean) => { - this.userHasPremiumAccess = canAccessPremium; - }); - } - - ngOnDestroy() { - this.componentIsDestroyed$.next(true); - this.componentIsDestroyed$.complete(); - } - - launchCipher() { - this.launchEvent.emit(this.cipher); - } - - async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) { - if ( - this.cipher.reprompt !== CipherRepromptType.None && - this.passwordRepromptService.protectedFields().includes(aType) && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - return; - } - - if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) { - return; - } else if (aType === "TOTP") { - value = await this.totpService.getCode(value); - } - - if (!cipher.viewPassword) { - return; - } - - this.platformUtilsService.copyToClipboard(value, { window: window }); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); - - if (typeI18nKey === "password") { - // 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.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); - } else if (typeI18nKey === "verificationCodeTotp") { - // 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.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, cipher.id); - } else if (typeI18nKey === "securityCode") { - // 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.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); - } - } - - displayTotpCopyButton(cipher: CipherView) { - return ( - (cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || this.userHasPremiumAccess) - ); - } - - view() { - this.onView.emit(this.cipher); - } -} diff --git a/apps/browser/src/vault/popup/components/cipher-row.component.html b/apps/browser/src/vault/popup/components/cipher-row.component.html deleted file mode 100644 index 8ac9147cb92..00000000000 --- a/apps/browser/src/vault/popup/components/cipher-row.component.html +++ /dev/null @@ -1,51 +0,0 @@ -
    -
    - - - -
    -
    diff --git a/apps/browser/src/vault/popup/components/cipher-row.component.ts b/apps/browser/src/vault/popup/components/cipher-row.component.ts deleted file mode 100644 index 6b71470a86b..00000000000 --- a/apps/browser/src/vault/popup/components/cipher-row.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, Output } from "@angular/core"; - -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; - -@Component({ - selector: "app-cipher-row", - templateUrl: "cipher-row.component.html", -}) -export class CipherRowComponent { - @Output() onSelected = new EventEmitter(); - @Output() launchEvent = new EventEmitter(); - @Output() onView = new EventEmitter(); - @Input() cipher: CipherView; - @Input() last: boolean; - @Input() showView = false; - @Input() title: string; - - selectCipher(c: CipherView) { - this.onSelected.emit(c); - } - - launchCipher(c: CipherView) { - this.launchEvent.emit(c); - } - - viewCipher(c: CipherView) { - this.onView.emit(c); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html similarity index 100% rename from apps/browser/src/vault/popup/components/vault/vault-v2.component.html rename to apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts similarity index 95% rename from apps/browser/src/vault/popup/components/vault/vault-v2.component.ts rename to apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 68aa40cbf62..9970c115bb7 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -18,12 +18,14 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page import { VaultPopupItemsService } from "../../services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service"; import { VaultUiOnboardingService } from "../../services/vault-ui-onboarding.service"; -import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "../vault-v2"; + import { NewItemDropdownV2Component, NewItemInitialValues, -} from "../vault-v2/new-item-dropdown/new-item-dropdown-v2.component"; -import { VaultHeaderV2Component } from "../vault-v2/vault-header/vault-header-v2.component"; +} from "./new-item-dropdown/new-item-dropdown-v2.component"; +import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; + +import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; enum VaultState { Empty, diff --git a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.html b/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.html deleted file mode 100644 index 8464655c20b..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.html +++ /dev/null @@ -1,140 +0,0 @@ -
    -

    - {{ "customFields" | i18n }} -

    -
    - -
    -
    - - - -
    - - - - - - - -
    - - -
    - -
    -
    - -
    -
    -
    - -
    - - - -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.ts deleted file mode 100644 index 6992455a8a6..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit-custom-fields.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from "@angular/core"; - -import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/vault/components/add-edit-custom-fields.component"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -@Component({ - selector: "app-vault-add-edit-custom-fields", - templateUrl: "add-edit-custom-fields.component.html", -}) -export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { - constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { - super(i18nService, eventCollectionService); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.html b/apps/browser/src/vault/popup/components/vault/add-edit.component.html deleted file mode 100644 index fb1efbbbd79..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.html +++ /dev/null @@ -1,826 +0,0 @@ -
    -
    -
    - -
    -

    - {{ title }} -

    -
    - -
    -
    -
    - - {{ "personalOwnershipPolicyInEffect" | i18n }} - -
    -

    - {{ "itemInformation" | i18n }} -

    -
    -
    - - -
    -
    - - -
    - -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - - - -
    -
    - - -
    -
    -
    - -
    - {{ "typePasskey" | i18n }} - {{ "dateCreated" | i18n }} - {{ cipher.login.fido2Credentials[0].creationDate | date: "short" }} -
    -
    -
    -
    - -
    -
    - - -
    -
    - - - -
    -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    -
    - -
    -
    -
    - - - - - - - -
    -
    - - - - - - - -
    -
    - - -
    -
    -
    - - -
    -
    - -
    -
    -
    - -
    -
    - - - - - - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - {{ "sshPrivateKey" | i18n }} - {{ cipher.sshKey.privateKey }} -
    -
    - {{ "sshPublicKey" | i18n }} - {{ cipher.sshKey.publicKey }} -
    -
    - {{ "sshKeyFingerprint" | i18n }} - {{ cipher.sshKey.keyFingerprint }} -
    -
    -
    -
    -
    -
    - -
    - -
    - - - - - - -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    - - -
    -
    -
    -

    - -

    -
    -
    - -
    -
    -
    - - -
    -

    - {{ "ownership" | i18n }} -

    -
    -
    - - -
    -
    -
    -
    -

    - {{ "collections" | i18n }} -

    -
    -
    - {{ "noCollectionsInList" | i18n }} -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts deleted file mode 100644 index 39414217b0d..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ /dev/null @@ -1,417 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe, Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import qrcodeParser from "qrcode-parser"; -import { firstValueFrom } from "rxjs"; -import { first } from "rxjs/operators"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { BrowserFido2UserInterfaceSession } from "../../../../autofill/fido2/services/browser-fido2-user-interface.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { PopupCloseWarningService } from "../../../../popup/services/popup-close-warning.service"; -import { Fido2UserVerificationService } from "../../../services/fido2-user-verification.service"; -import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data"; -import { closeAddEditVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window"; - -@Component({ - selector: "app-vault-add-edit", - templateUrl: "add-edit.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class AddEditComponent extends BaseAddEditComponent implements OnInit { - currentUris: string[]; - showAttachments = true; - openAttachmentsInPopup: boolean; - showAutoFillOnPageLoadOptions: boolean; - - private fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - cipherService: CipherService, - folderService: FolderService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - accountService: AccountService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - collectionService: CollectionService, - messagingService: MessagingService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - private popupCloseWarningService: PopupCloseWarningService, - organizationService: OrganizationService, - passwordRepromptService: PasswordRepromptService, - logService: LogService, - dialogService: DialogService, - datePipe: DatePipe, - configService: ConfigService, - private fido2UserVerificationService: Fido2UserVerificationService, - cipherAuthorizationService: CipherAuthorizationService, - ) { - super( - cipherService, - folderService, - i18nService, - platformUtilsService, - auditService, - accountService, - collectionService, - messagingService, - eventCollectionService, - policyService, - logService, - passwordRepromptService, - organizationService, - dialogService, - window, - datePipe, - configService, - cipherAuthorizationService, - ); - } - - async ngOnInit() { - await super.ngOnInit(); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } - if (params.folderId) { - this.folderId = params.folderId; - } - if (params.collectionId) { - this.collectionId = params.collectionId; - const collection = this.writeableCollections.find((c) => c.id === params.collectionId); - if (collection != null) { - this.collectionIds = [collection.id]; - this.organizationId = collection.organizationId; - } - } - if (params.type) { - const type = parseInt(params.type, null); - this.type = type; - } - this.editMode = !params.cipherId; - - if (params.cloneMode != null) { - this.cloneMode = params.cloneMode === "true"; - } - if (params.selectedVault) { - this.organizationId = params.selectedVault; - } - - await this.load(); - - if (!this.editMode || this.cloneMode) { - // Only allow setting username if there's no existing value - if ( - params.username && - (this.cipher.login.username == null || this.cipher.login.username === "") - ) { - this.cipher.login.username = params.username; - } - - if (params.name && (this.cipher.name == null || this.cipher.name === "")) { - this.cipher.name = params.name; - } - if ( - params.uri && - this.cipher.login.uris[0] && - (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === "") - ) { - this.cipher.login.uris[0].uri = params.uri; - } - } - - this.openAttachmentsInPopup = BrowserPopupUtils.inPopup(window); - - if (this.inAddEditPopoutWindow()) { - BrowserApi.messageListener("add-edit-popout", this.handleExtensionMessage.bind(this)); - } - }); - - if (!this.editMode) { - const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); - this.currentUris = - tabs == null - ? null - : tabs.filter((tab) => tab.url != null && tab.url !== "").map((tab) => tab.url); - } - - this.setFocus(); - - if (BrowserPopupUtils.inPopout(window)) { - this.popupCloseWarningService.enable(); - } - } - - async load() { - await super.load(); - this.showAutoFillOnPageLoadOptions = - this.cipher.type === CipherType.Login && - (await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$)); - } - - async submit(): Promise { - const fido2SessionData = await firstValueFrom(this.fido2PopoutSessionData$); - const { isFido2Session, sessionId, userVerification } = fido2SessionData; - const inFido2PopoutWindow = BrowserPopupUtils.inPopout(window) && isFido2Session; - - // normalize card expiry year on save - if (this.cipher.type === this.cipherType.Card) { - this.cipher.card.expYear = normalizeExpiryYearFormat(this.cipher.card.expYear); - } - - // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - if ( - inFido2PopoutWindow && - !(await this.handleFido2UserVerification(sessionId, userVerification)) - ) { - return false; - } - - const success = await super.submit(); - if (!success) { - return false; - } - - if (BrowserPopupUtils.inPopout(window)) { - this.popupCloseWarningService.disable(); - } - - if (inFido2PopoutWindow) { - BrowserFido2UserInterfaceSession.confirmNewCredentialResponse( - sessionId, - this.cipher.id, - userVerification, - ); - return true; - } - - if (this.inAddEditPopoutWindow()) { - this.messagingService.send("addEditCipherSubmitted"); - await closeAddEditVaultItemPopout(1000); - return true; - } - - if (this.cloneMode) { - // 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(["/tabs/vault"]); - } else { - this.location.back(); - } - return true; - } - - attachments() { - super.attachments(); - - if (this.openAttachmentsInPopup) { - const destinationUrl = this.router - .createUrlTree(["/attachments"], { queryParams: { cipherId: this.cipher.id } }) - .toString(); - const currentBaseUrl = window.location.href.replace(this.router.url, ""); - // 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, currentBaseUrl + destinationUrl); - } 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(["/attachments"], { queryParams: { cipherId: this.cipher.id } }); - } - } - - editCollections() { - super.editCollections(); - if (this.cipher.organizationId != 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(["/collections"], { queryParams: { cipherId: this.cipher.id } }); - } - } - - async cancel() { - super.cancel(); - - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - if (BrowserPopupUtils.inPopout(window) && sessionData.isFido2Session) { - this.popupCloseWarningService.disable(); - BrowserFido2UserInterfaceSession.abortPopout(sessionData.sessionId); - return; - } - - if (this.inAddEditPopoutWindow()) { - // 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 - closeAddEditVaultItemPopout(); - return; - } - - this.location.back(); - } - - async generateUsername(): Promise { - const confirmed = await super.generateUsername(); - if (confirmed) { - await this.saveCipherState(); - // 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(["generator"], { queryParams: { type: "username" } }); - } - return confirmed; - } - - async generatePassword(): Promise { - const confirmed = await super.generatePassword(); - if (confirmed) { - await this.saveCipherState(); - // 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(["generator"], { queryParams: { type: "password" } }); - } - return confirmed; - } - - async delete(): Promise { - const confirmed = await super.delete(); - 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 - this.router.navigate(["/tabs/vault"]); - } - return confirmed; - } - - toggleUriInput(uri: LoginUriView) { - const u = uri as any; - u.showCurrentUris = !u.showCurrentUris; - } - - allowOwnershipOptions(): boolean { - return ( - (!this.editMode || this.cloneMode) && - this.ownershipOptions && - (this.ownershipOptions.length > 1 || !this.allowPersonal) - ); - } - - private saveCipherState() { - return this.cipherService.setAddEditCipherInfo({ - cipher: this.cipher, - collectionIds: - this.collections == null - ? [] - : this.collections.filter((c) => (c as any).checked).map((c) => c.id), - }); - } - - private setFocus() { - window.setTimeout(() => { - if (this.editMode) { - return; - } - - if (this.cipher.name != null && this.cipher.name !== "") { - document.getElementById("loginUsername").focus(); - } else { - document.getElementById("name").focus(); - } - }, 200); - } - - repromptChanged() { - super.repromptChanged(); - - if (!this.showAutoFillOnPageLoadOptions) { - return; - } - - if (this.reprompt) { - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("passwordRepromptDisabledAutofillOnPageLoad"), - ); - return; - } - - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("autofillOnPageLoadSetToDefault"), - ); - } - - private inAddEditPopoutWindow() { - return BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.addEditVaultItem); - } - - async captureTOTPFromTab() { - try { - const screenshot = await BrowserApi.captureVisibleTab(); - const data = await qrcodeParser(screenshot); - const url = new URL(data.toString()); - if (url.protocol == "otpauth:" && url.searchParams.has("secret")) { - this.cipher.login.totp = data.toString(); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("totpCaptureSuccess"), - ); - } - } catch (e) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("totpCaptureError"), - ); - } - } - - private handleExtensionMessage(message: { [key: string]: any; command: string }) { - if (message.command === "inlineAutofillMenuRefreshAddEditCipher") { - this.load().catch((error) => this.logService.error(error)); - } - } - - // TODO: Remove and use fido2 user verification service once user verification for passkeys is approved for production. - // Be sure to make the same changes to add-edit-v2.component.ts if applicable - private async handleFido2UserVerification( - sessionId: string, - userVerification: boolean, - ): Promise { - // We are bypassing user verification pending approval for production. - return true; - } -} diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.html b/apps/browser/src/vault/popup/components/vault/attachments.component.html deleted file mode 100644 index b95dc69af8f..00000000000 --- a/apps/browser/src/vault/popup/components/vault/attachments.component.html +++ /dev/null @@ -1,72 +0,0 @@ -
    -
    -
    - - -
    -

    - {{ "attachments" | i18n }} -

    -
    - -
    -
    -
    -
    -
    -
    -
    - {{ a.fileName }} -
    - {{ a.sizeName }} -
    - -
    -
    -
    -
    -
    -

    - {{ "newAttachment" | i18n }} -

    -
    -
    - - -
    -
    - -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.ts b/apps/browser/src/vault/popup/components/vault/attachments.component.ts deleted file mode 100644 index b63b743b9fa..00000000000 --- a/apps/browser/src/vault/popup/components/vault/attachments.component.ts +++ /dev/null @@ -1,82 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Component({ - selector: "app-vault-attachments", - templateUrl: "attachments.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class AttachmentsComponent extends BaseAttachmentsComponent implements OnInit { - openedAttachmentsInPopup: boolean; - - constructor( - cipherService: CipherService, - i18nService: I18nService, - keyService: KeyService, - encryptService: EncryptService, - platformUtilsService: PlatformUtilsService, - apiService: ApiService, - private location: Location, - private route: ActivatedRoute, - stateService: StateService, - logService: LogService, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - billingAccountProfileStateService: BillingAccountProfileStateService, - accountService: AccountService, - toastService: ToastService, - ) { - super( - cipherService, - i18nService, - keyService, - encryptService, - platformUtilsService, - apiService, - window, - logService, - stateService, - fileDownloadService, - dialogService, - billingAccountProfileStateService, - accountService, - toastService, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.cipherId = params.cipherId; - await this.init(); - }); - - this.openedAttachmentsInPopup = history.length === 1; - } - - back() { - this.location.back(); - } - - close() { - window.close(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.html b/apps/browser/src/vault/popup/components/vault/collections.component.html deleted file mode 100644 index 36c1336c5b4..00000000000 --- a/apps/browser/src/vault/popup/components/vault/collections.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
    -
    -
    - -
    -

    - {{ "collections" | i18n }} -

    -
    - -
    -
    -
    -
    -
    -
    - {{ "noCollectionsInList" | i18n }} -
    -
    -
    -
    - - -
    -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.ts b/apps/browser/src/vault/popup/components/vault/collections.component.ts deleted file mode 100644 index 407f87e996c..00000000000 --- a/apps/browser/src/vault/popup/components/vault/collections.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { ToastService } from "@bitwarden/components"; - -@Component({ - selector: "app-vault-collections", - templateUrl: "collections.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class CollectionsComponent extends BaseCollectionsComponent implements OnInit { - constructor( - collectionService: CollectionService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - cipherService: CipherService, - organizationService: OrganizationService, - private route: ActivatedRoute, - private location: Location, - logService: LogService, - accountService: AccountService, - toastService: ToastService, - ) { - super( - collectionService, - platformUtilsService, - i18nService, - cipherService, - organizationService, - logService, - accountService, - toastService, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.onSavedCollections.subscribe(() => { - this.back(); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.cipherId = params.cipherId; - await this.load(); - }); - } - - back() { - this.location.back(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.html b/apps/browser/src/vault/popup/components/vault/current-tab.component.html deleted file mode 100644 index bb8a401da62..00000000000 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.html +++ /dev/null @@ -1,95 +0,0 @@ - -

    {{ "currentTab" | i18n }}

    -
    - - -
    - -
    - -
    -
    -
    -
    - -
    - - -
    -

    - {{ "typeLogins" | i18n }} - {{ loginCiphers.length }} -

    -
    - - -
    -

    {{ "autoFillInfo" | i18n }}

    - -
    -
    -
    -
    -

    - {{ "cards" | i18n }} - {{ cardCiphers.length }} -

    -
    - -
    -
    -
    -

    - {{ "identities" | i18n }} - {{ identityCiphers.length }} -

    -
    - -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts deleted file mode 100644 index 156a708015f..00000000000 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ /dev/null @@ -1,354 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { Subject, firstValueFrom, from, Subscription } from "rxjs"; -import { debounceTime, switchMap, takeUntil } from "rxjs/operators"; - -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { AutofillService } from "../../../../autofill/services/abstractions/autofill.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const BroadcasterSubscriptionId = "CurrentTabComponent"; - -@Component({ - selector: "app-current-tab", - templateUrl: "current-tab.component.html", -}) -export class CurrentTabComponent implements OnInit, OnDestroy { - pageDetails: any[] = []; - tab: chrome.tabs.Tab; - cardCiphers: CipherView[]; - identityCiphers: CipherView[]; - loginCiphers: CipherView[]; - url: string; - hostname: string; - searchText: string; - inSidebar = false; - searchTypeSearch = false; - loaded = false; - isLoading = false; - showOrganizations = false; - showHowToAutofill = false; - autofillCalloutText: string; - protected search$ = new Subject(); - private destroy$ = new Subject(); - private collectPageDetailsSubscription: Subscription; - - private totpCode: string; - private totpTimeout: number; - private loadedTimeout: number; - private searchTimeout: number; - - constructor( - private platformUtilsService: PlatformUtilsService, - private cipherService: CipherService, - private autofillService: AutofillService, - private i18nService: I18nService, - private router: Router, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private syncService: SyncService, - private searchService: SearchService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - private passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService, - private vaultFilterService: VaultFilterService, - private vaultSettingsService: VaultSettingsService, - ) {} - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.inSidebar = BrowserPopupUtils.inSidebar(window); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (this.isLoading) { - window.setTimeout(() => { - // 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.load(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - - if (!this.syncService.syncInProgress) { - await this.load(); - await this.setCallout(); - } else { - this.loadedTimeout = window.setTimeout(async () => { - if (!this.isLoading) { - await this.load(); - await this.setCallout(); - } - }, 5000); - } - - this.search$ - .pipe( - debounceTime(500), - switchMap(() => { - return from(this.searchVault()); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - const autofillOnPageLoadOrgPolicy = await firstValueFrom( - this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$, - ); - const autofillOnPageLoadPolicyToastHasDisplayed = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoadPolicyToastHasDisplayed$, - ); - - // If the org "autofill on page load" policy is set, set the user setting to match it - // @TODO override user setting instead of overwriting - if (autofillOnPageLoadOrgPolicy === true) { - await this.autofillSettingsService.setAutofillOnPageLoad(true); - - if (!autofillOnPageLoadPolicyToastHasDisplayed) { - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("autofillPageLoadPolicyActivated"), - ); - - await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(true); - } - } - - // If the org policy is ever disabled after being enabled, reset the toast notification - if (!autofillOnPageLoadOrgPolicy && autofillOnPageLoadPolicyToastHasDisplayed) { - await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(false); - } - } - - ngOnDestroy() { - window.clearTimeout(this.loadedTimeout); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - - this.destroy$.next(); - this.destroy$.complete(); - } - - async refresh() { - await this.load(); - } - - addCipher() { - // 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(["/add-cipher"], { - queryParams: { - name: this.hostname, - uri: this.url, - selectedVault: this.vaultFilterService.getVaultFilter().selectedOrganizationId, - }, - }); - } - - viewCipher(cipher: CipherView) { - // 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(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); - } - - async fillCipher(cipher: CipherView, closePopupDelay?: number) { - if ( - cipher.reprompt !== CipherRepromptType.None && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - return; - } - - this.totpCode = null; - if (this.totpTimeout != null) { - window.clearTimeout(this.totpTimeout); - } - - if (this.pageDetails == null || this.pageDetails.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - return; - } - - try { - this.totpCode = await this.autofillService.doAutoFill({ - tab: this.tab, - cipher: cipher, - pageDetails: this.pageDetails, - doc: window.document, - fillNewPassword: true, - allowTotpAutofill: true, - }); - if (this.totpCode != null) { - this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); - } - if (BrowserPopupUtils.inPopup(window)) { - if (!closePopupDelay) { - if (this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()) { - BrowserApi.closePopup(window); - } else { - // Slight delay to fix bug in Chromium browsers where popup closes without copying totp to clipboard - setTimeout(() => BrowserApi.closePopup(window), 50); - } - } else { - setTimeout(() => BrowserApi.closePopup(window), closePopupDelay); - } - } - } catch { - this.ngZone.run(() => { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - this.changeDetectorRef.detectChanges(); - }); - } - } - - async searchVault() { - if (!(await this.searchService.isSearchable(this.searchText))) { - return; - } - - await this.router.navigate(["/tabs/vault"], { queryParams: { searchText: this.searchText } }); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { - BrowserApi.closePopup(window); - } - } - - protected async load() { - this.isLoading = false; - this.tab = await BrowserApi.getTabFromCurrentWindow(); - - if (this.tab != null) { - this.url = this.tab.url; - } else { - this.loginCiphers = []; - this.isLoading = this.loaded = true; - return; - } - - this.pageDetails = []; - this.collectPageDetailsSubscription?.unsubscribe(); - this.collectPageDetailsSubscription = this.autofillService - .collectPageDetailsFromTab$(this.tab) - .pipe(takeUntil(this.destroy$)) - .subscribe((pageDetails) => (this.pageDetails = pageDetails)); - - this.hostname = Utils.getHostname(this.url); - const otherTypes: CipherType[] = []; - const dontShowCards = !(await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$)); - const dontShowIdentities = !(await firstValueFrom( - this.vaultSettingsService.showIdentitiesCurrentTab$, - )); - this.showOrganizations = await this.organizationService.hasOrganizations(); - if (!dontShowCards) { - otherTypes.push(CipherType.Card); - } - if (!dontShowIdentities) { - otherTypes.push(CipherType.Identity); - } - - const ciphers = await this.cipherService.getAllDecryptedForUrl( - this.url, - otherTypes.length > 0 ? otherTypes : null, - ); - - this.loginCiphers = []; - this.cardCiphers = []; - this.identityCiphers = []; - - ciphers.forEach((c) => { - if (!this.vaultFilterService.filterCipherForSelectedVault(c)) { - switch (c.type) { - case CipherType.Login: - this.loginCiphers.push(c); - break; - case CipherType.Card: - this.cardCiphers.push(c); - break; - case CipherType.Identity: - this.identityCiphers.push(c); - break; - default: - break; - } - } - }); - - if (this.loginCiphers.length) { - this.loginCiphers = this.loginCiphers.sort((a, b) => - this.cipherService.sortCiphersByLastUsedThenName(a, b), - ); - } - - this.isLoading = this.loaded = true; - } - - async goToSettings() { - // 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(["autofill"]); - } - - async dismissCallout() { - await this.autofillSettingsService.setAutofillOnPageLoadCalloutIsDismissed(true); - this.showHowToAutofill = false; - } - - private async setCallout() { - const inlineMenuVisibilityIsOff = - (await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$)) === - AutofillOverlayVisibility.Off; - - this.showHowToAutofill = - this.loginCiphers.length > 0 && - inlineMenuVisibilityIsOff && - !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$)) && - !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoadCalloutIsDismissed$)); - - if (this.showHowToAutofill) { - const autofillCommand = await this.platformUtilsService.getAutofillKeyboardShortcut(); - await this.setAutofillCalloutText(autofillCommand); - } - } - - private setAutofillCalloutText(command: string) { - if (command) { - this.autofillCalloutText = this.i18nService.t("autofillSelectInfoWithCommand", command); - } else { - this.autofillCalloutText = this.i18nService.t("autofillSelectInfoWithoutCommand"); - } - } -} diff --git a/apps/browser/src/vault/popup/components/vault/password-history.component.html b/apps/browser/src/vault/popup/components/vault/password-history.component.html deleted file mode 100644 index 6286aa1022d..00000000000 --- a/apps/browser/src/vault/popup/components/vault/password-history.component.html +++ /dev/null @@ -1,40 +0,0 @@ -
    -
    - -
    -

    - {{ "passwordHistory" | i18n }} -

    -
    -
    -
    -
    -
    -
    -
    -
    - - {{ h.lastUsedDate | date: "medium" }} -
    -
    -
    - -
    -
    -
    -
    -
    -

    {{ "noPasswordsInList" | i18n }}

    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/password-history.component.ts b/apps/browser/src/vault/popup/components/vault/password-history.component.ts deleted file mode 100644 index bf1b4ea7717..00000000000 --- a/apps/browser/src/vault/popup/components/vault/password-history.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; - -@Component({ - selector: "app-password-history", - templateUrl: "password-history.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class PasswordHistoryComponent extends BasePasswordHistoryComponent implements OnInit { - constructor( - cipherService: CipherService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - accountService: AccountService, - private location: Location, - private route: ActivatedRoute, - ) { - super(cipherService, platformUtilsService, i18nService, accountService, window); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } else { - this.close(); - } - await this.init(); - }); - } - - close() { - this.location.back(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/share.component.html b/apps/browser/src/vault/popup/components/vault/share.component.html deleted file mode 100644 index 46aaecd06b8..00000000000 --- a/apps/browser/src/vault/popup/components/vault/share.component.html +++ /dev/null @@ -1,77 +0,0 @@ -
    - -
    -
    - -
    -

    - {{ "moveToOrganization" | i18n }} -

    -
    - -
    -
    -
    -
    -
    -
    - {{ "noOrganizationsList" | i18n }} -
    -
    -
    -
    - - -
    -
    - -
    -
    -

    - {{ "collections" | i18n }} -

    -
    -
    - {{ "noCollectionsInList" | i18n }} -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/share.component.ts b/apps/browser/src/vault/popup/components/vault/share.component.ts deleted file mode 100644 index 8e061665b73..00000000000 --- a/apps/browser/src/vault/popup/components/vault/share.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; - -@Component({ - selector: "app-vault-share", - templateUrl: "share.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class ShareComponent extends BaseShareComponent implements OnInit { - constructor( - collectionService: CollectionService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - logService: LogService, - cipherService: CipherService, - private route: ActivatedRoute, - private router: Router, - organizationService: OrganizationService, - accountService: AccountService, - ) { - super( - collectionService, - platformUtilsService, - i18nService, - cipherService, - logService, - organizationService, - accountService, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.onSharedCipher.subscribe(() => { - // 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(["view-cipher", { cipherId: this.cipherId }]); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.cipherId = params.cipherId; - await this.load(); - }); - } - - async submit(): Promise { - const success = await super.submit(); - if (success) { - this.cancel(); - } - return success; - } - - cancel() { - // 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(["/view-cipher"], { - replaceUrl: true, - queryParams: { cipherId: this.cipher.id }, - }); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html b/apps/browser/src/vault/popup/components/vault/vault-filter.component.html deleted file mode 100644 index bf557f74608..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.html +++ /dev/null @@ -1,238 +0,0 @@ - -
    - -
    -

    {{ "myVault" | i18n }}

    - -
    - -
    -
    -
    - -
    - - - -

    {{ "noItemsInList" | i18n }}

    - -
    -
    - -
    -

    - {{ "favorites" | i18n }} - {{ favoriteCiphers.length }} -

    -
    - - -
    -
    -
    -

    - {{ "types" | i18n }} - 4 -

    -
    - - - - - -
    -
    -
    -

    - {{ "folders" | i18n }} - {{ folderCount }} -

    -
    - -
    -
    -
    -

    - {{ "collections" | i18n }} - {{ nestedCollections.length }} -

    -
    - -
    -
    -
    -

    - {{ "noneFolder" | i18n }} -
    {{ noFolderCiphers.length }}
    -

    -
    - - -
    -
    -
    -

    - {{ "trash" | i18n }} - {{ deletedCount }} -

    -
    - -
    -
    -
    - -
    -

    {{ "noItemsInList" | i18n }}

    -
    - -
    -
    - - -
    -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts deleted file mode 100644 index d430568869c..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ /dev/null @@ -1,482 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { BehaviorSubject, Subject, firstValueFrom, from } from "rxjs"; -import { first, switchMap, takeUntil } from "rxjs/operators"; - -import { CollectionView } from "@bitwarden/admin-console/common"; -import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -import { BrowserGroupingsComponentState } from "../../../../models/browserGroupingsComponentState"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const ComponentId = "VaultComponent"; - -@Component({ - selector: "app-vault-filter", - templateUrl: "vault-filter.component.html", -}) -export class VaultFilterComponent implements OnInit, OnDestroy { - get showNoFolderCiphers(): boolean { - return ( - this.noFolderCiphers != null && - this.noFolderCiphers.length < this.noFolderListSize && - this.collections.length === 0 - ); - } - - get folderCount(): number { - return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1); - } - folders: FolderView[]; - nestedFolders: TreeNode[]; - collections: CollectionView[]; - nestedCollections: TreeNode[]; - loaded = false; - cipherType = CipherType; - ciphers: CipherView[]; - favoriteCiphers: CipherView[]; - noFolderCiphers: CipherView[]; - folderCounts = new Map(); - collectionCounts = new Map(); - typeCounts = new Map(); - state: BrowserGroupingsComponentState; - showLeftHeader = true; - searchPending = false; - searchTypeSearch = false; - deletedCount = 0; - vaultFilter: VaultFilter; - selectedOrganization: string = null; - showCollections = true; - - isSshKeysEnabled = false; - - private loadedTimeout: number; - private selectedTimeout: number; - private preventSelected = false; - private noFolderListSize = 100; - private searchTimeout: any = null; - private hasSearched = false; - private hasLoadedAllCiphers = false; - private allCiphers: CipherView[] = null; - private destroy$ = new Subject(); - private _searchText$ = new BehaviorSubject(""); - private isSearchable: boolean = false; - - get searchText() { - return this._searchText$.value; - } - set searchText(value: string) { - this._searchText$.next(value); - } - - constructor( - private i18nService: I18nService, - private cipherService: CipherService, - private router: Router, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private route: ActivatedRoute, - private syncService: SyncService, - private platformUtilsService: PlatformUtilsService, - private searchService: SearchService, - private location: Location, - private vaultFilterService: VaultFilterService, - private vaultBrowserStateService: VaultBrowserStateService, - private configService: ConfigService, - ) { - this.noFolderListSize = 100; - } - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.showLeftHeader = !( - BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() - ); - await this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - window.setTimeout(() => { - // 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.load(); - }, 500); - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - - const restoredScopeState = await this.restoreState(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); - if (this.state?.searchText) { - this.searchText = this.state.searchText; - } else if (params.searchText) { - this.searchText = params.searchText; - this.location.replaceState("vault"); - } - - if (!this.syncService.syncInProgress) { - // 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.load(); - } else { - this.loadedTimeout = window.setTimeout(() => { - if (!this.loaded) { - // 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.load(); - } - }, 5000); - } - - if (!this.syncService.syncInProgress || restoredScopeState) { - // 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.setContentScrollY(window, this.state?.scrollY); - } - }); - - this._searchText$ - .pipe( - switchMap((searchText) => from(this.searchService.isSearchable(searchText))), - takeUntil(this.destroy$), - ) - .subscribe((isSearchable) => { - this.isSearchable = isSearchable; - }); - - this.isSshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem); - } - - ngOnDestroy() { - if (this.loadedTimeout != null) { - window.clearTimeout(this.loadedTimeout); - } - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - // 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.saveState(); - this.broadcasterService.unsubscribe(ComponentId); - this.destroy$.next(); - this.destroy$.complete(); - } - - async load() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - - this.updateSelectedOrg(); - await this.loadCollectionsAndFolders(); - await this.loadCiphers(); - - if (this.showNoFolderCiphers && this.nestedFolders.length > 0) { - // Remove "No Folder" from folder listing - this.nestedFolders = this.nestedFolders.slice(0, this.nestedFolders.length - 1); - } - - this.loaded = true; - } - - async loadCiphers() { - this.allCiphers = await this.cipherService.getAllDecrypted(); - if (!this.hasLoadedAllCiphers) { - this.hasLoadedAllCiphers = !(await this.searchService.isSearchable(this.searchText)); - } - await this.search(null); - this.getCounts(); - } - - async loadCollections() { - const allCollections = await this.vaultFilterService.buildCollections( - this.selectedOrganization, - ); - this.collections = allCollections.fullList; - this.nestedCollections = allCollections.nestedList; - } - - async loadFolders() { - const allFolders = await firstValueFrom( - this.vaultFilterService.buildNestedFolders(this.selectedOrganization), - ); - this.folders = allFolders.fullList; - this.nestedFolders = allFolders.nestedList; - } - - async search(timeout: number = null) { - this.searchPending = false; - if (this.searchTimeout != null) { - clearTimeout(this.searchTimeout); - } - const filterDeleted = (c: CipherView) => !c.isDeleted; - if (timeout == null) { - this.hasSearched = this.isSearchable; - this.ciphers = await this.searchService.searchCiphers( - this.searchText, - filterDeleted, - this.allCiphers, - ); - this.ciphers = this.ciphers.filter( - (c) => !this.vaultFilterService.filterCipherForSelectedVault(c), - ); - return; - } - this.searchPending = true; - this.searchTimeout = setTimeout(async () => { - this.hasSearched = this.isSearchable; - if (!this.hasLoadedAllCiphers && !this.hasSearched) { - await this.loadCiphers(); - } else { - this.ciphers = await this.searchService.searchCiphers( - this.searchText, - filterDeleted, - this.allCiphers, - ); - } - this.ciphers = this.ciphers.filter( - (c) => !this.vaultFilterService.filterCipherForSelectedVault(c), - ); - this.searchPending = false; - }, timeout); - } - - async selectType(type: CipherType) { - // 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(["/ciphers"], { queryParams: { type: type } }); - } - - async selectFolder(folder: FolderView) { - // 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(["/ciphers"], { queryParams: { folderId: folder.id || "none" } }); - } - - async selectCollection(collection: CollectionView) { - // 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(["/ciphers"], { queryParams: { collectionId: collection.id } }); - } - - async selectTrash() { - // 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(["/ciphers"], { queryParams: { deleted: true } }); - } - - async selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - // 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(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); - } - this.preventSelected = false; - }, 200); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; - } - - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); - // 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 - BrowserApi.createNewTab(cipher.login.launchUri); - if (BrowserPopupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } - } - - async addCipher() { - // 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(["/add-cipher"], { - queryParams: { selectedVault: this.vaultFilter.selectedOrganizationId }, - }); - } - - async vaultFilterChanged() { - if (this.showSearching) { - await this.search(); - } - this.updateSelectedOrg(); - await this.loadCollectionsAndFolders(); - this.getCounts(); - } - - updateSelectedOrg() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - if (this.vaultFilter.selectedOrganizationId != null) { - this.selectedOrganization = this.vaultFilter.selectedOrganizationId; - } else { - this.selectedOrganization = null; - } - } - - getCounts() { - let favoriteCiphers: CipherView[] = null; - let noFolderCiphers: CipherView[] = null; - const folderCounts = new Map(); - const collectionCounts = new Map(); - const typeCounts = new Map(); - - this.deletedCount = this.allCiphers.filter( - (c) => c.isDeleted && !this.vaultFilterService.filterCipherForSelectedVault(c), - ).length; - - this.ciphers?.forEach((c) => { - if (!this.vaultFilterService.filterCipherForSelectedVault(c)) { - if (c.isDeleted) { - return; - } - if (c.favorite) { - if (favoriteCiphers == null) { - favoriteCiphers = []; - } - favoriteCiphers.push(c); - } - - if (c.folderId == null) { - if (noFolderCiphers == null) { - noFolderCiphers = []; - } - noFolderCiphers.push(c); - } - - if (typeCounts.has(c.type)) { - typeCounts.set(c.type, typeCounts.get(c.type) + 1); - } else { - typeCounts.set(c.type, 1); - } - - if (folderCounts.has(c.folderId)) { - folderCounts.set(c.folderId, folderCounts.get(c.folderId) + 1); - } else { - folderCounts.set(c.folderId, 1); - } - - if (c.collectionIds != null) { - c.collectionIds.forEach((colId) => { - if (collectionCounts.has(colId)) { - collectionCounts.set(colId, collectionCounts.get(colId) + 1); - } else { - collectionCounts.set(colId, 1); - } - }); - } - } - }); - - this.favoriteCiphers = favoriteCiphers; - this.noFolderCiphers = noFolderCiphers; - this.typeCounts = typeCounts; - this.folderCounts = folderCounts; - this.collectionCounts = collectionCounts; - } - - showSearching() { - return this.hasSearched || (!this.searchPending && this.isSearchable); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { - BrowserApi.closePopup(window); - } - } - - private async loadCollectionsAndFolders() { - this.showCollections = !this.vaultFilter.myVaultOnly; - await this.loadFolders(); - await this.loadCollections(); - } - - private async saveState() { - this.state = Object.assign(new BrowserGroupingsComponentState(), { - scrollY: BrowserPopupUtils.getContentScrollY(window), - searchText: this.searchText, - favoriteCiphers: this.favoriteCiphers, - noFolderCiphers: this.noFolderCiphers, - ciphers: this.ciphers, - collectionCounts: this.collectionCounts, - folderCounts: this.folderCounts, - typeCounts: this.typeCounts, - folders: this.folders, - collections: this.collections, - deletedCount: this.deletedCount, - }); - await this.vaultBrowserStateService.setBrowserGroupingsComponentState(this.state); - } - - private async restoreState(): Promise { - this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); - if (this.state == null) { - return false; - } - - if (this.state.favoriteCiphers != null) { - this.favoriteCiphers = this.state.favoriteCiphers; - } - if (this.state.noFolderCiphers != null) { - this.noFolderCiphers = this.state.noFolderCiphers; - } - if (this.state.ciphers != null) { - this.ciphers = this.state.ciphers; - } - if (this.state.collectionCounts != null) { - this.collectionCounts = this.state.collectionCounts; - } - if (this.state.folderCounts != null) { - this.folderCounts = this.state.folderCounts; - } - if (this.state.typeCounts != null) { - this.typeCounts = this.state.typeCounts; - } - if (this.state.folders != null) { - this.folders = this.state.folders; - } - if (this.state.collections != null) { - this.collections = this.state.collections; - } - if (this.state.deletedCount != null) { - this.deletedCount = this.state.deletedCount; - } - - return true; - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-items.component.html b/apps/browser/src/vault/popup/components/vault/vault-items.component.html deleted file mode 100644 index f10688554d9..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-items.component.html +++ /dev/null @@ -1,123 +0,0 @@ -
    -
    - -
    -

    {{ "myVault" | i18n }}

    - -
    - -
    -
    -
    - - -
    -

    - {{ "folders" | i18n }} -

    -
    - -
    -
    -
    -

    - {{ "collections" | i18n }} -

    -
    - -
    -
    -
    - -
    - -
    - - - -

    {{ "noItemsInList" | i18n }}

    - -
    -
    -
    - -
    -

    - {{ groupingTitle }} - {{ isSearching() ? ciphers.length : ciphers.length }} -

    -
    - -
    -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts deleted file mode 100644 index 387afcfe217..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts +++ /dev/null @@ -1,316 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; -import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -import { BrowserComponentState } from "../../../../models/browserComponentState"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const ComponentId = "VaultItemsComponent"; - -@Component({ - selector: "app-vault-items", - templateUrl: "vault-items.component.html", -}) -export class VaultItemsComponent extends BaseVaultItemsComponent implements OnInit, OnDestroy { - groupingTitle: string; - state: BrowserComponentState; - folderId: string = null; - collectionId: string = null; - type: CipherType = null; - nestedFolders: TreeNode[]; - nestedCollections: TreeNode[]; - searchTypeSearch = false; - showOrganizations = false; - vaultFilter: VaultFilter; - deleted = true; - noneFolder = false; - showVaultFilter = false; - - private selectedTimeout: number; - private preventSelected = false; - private applySavedState = true; - private scrollingContainer = "cdk-virtual-scroll-viewport"; - - constructor( - searchService: SearchService, - private organizationService: OrganizationService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private stateService: VaultBrowserStateService, - private i18nService: I18nService, - private collectionService: CollectionService, - private platformUtilsService: PlatformUtilsService, - cipherService: CipherService, - private vaultFilterService: VaultFilterService, - ) { - super(searchService, cipherService); - this.applySavedState = - (window as any).previousPopupUrl != null && - !(window as any).previousPopupUrl.startsWith("/ciphers"); - } - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.showOrganizations = await this.organizationService.hasOrganizations(); - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (this.applySavedState) { - this.state = await this.stateService.getBrowserVaultItemsComponentState(); - if (this.state?.searchText) { - this.searchText = this.state.searchText; - } - } - - if (params.deleted) { - this.showVaultFilter = true; - this.groupingTitle = this.i18nService.t("trash"); - this.searchPlaceholder = this.i18nService.t("searchTrash"); - await this.load(this.buildFilter(), true); - } else if (params.type) { - this.showVaultFilter = true; - this.searchPlaceholder = this.i18nService.t("searchType"); - this.type = parseInt(params.type, null); - switch (this.type) { - case CipherType.Login: - this.groupingTitle = this.i18nService.t("logins"); - break; - case CipherType.Card: - this.groupingTitle = this.i18nService.t("cards"); - break; - case CipherType.Identity: - this.groupingTitle = this.i18nService.t("identities"); - break; - case CipherType.SecureNote: - this.groupingTitle = this.i18nService.t("secureNotes"); - break; - case CipherType.SshKey: - this.groupingTitle = this.i18nService.t("sshKeys"); - break; - default: - break; - } - await this.load(this.buildFilter()); - } else if (params.folderId) { - this.showVaultFilter = true; - this.folderId = params.folderId === "none" ? null : params.folderId; - this.searchPlaceholder = this.i18nService.t("searchFolder"); - if (this.folderId != null) { - this.showOrganizations = false; - const folderNode = await this.vaultFilterService.getFolderNested(this.folderId); - if (folderNode != null && folderNode.node != null) { - this.groupingTitle = folderNode.node.name; - this.nestedFolders = - folderNode.children != null && folderNode.children.length > 0 - ? folderNode.children - : null; - } - } else { - this.noneFolder = true; - this.groupingTitle = this.i18nService.t("noneFolder"); - } - await this.load(this.buildFilter()); - } else if (params.collectionId) { - this.showVaultFilter = false; - this.collectionId = params.collectionId; - this.searchPlaceholder = this.i18nService.t("searchCollection"); - const collectionNode = await this.collectionService.getNested(this.collectionId); - if (collectionNode != null && collectionNode.node != null) { - this.groupingTitle = collectionNode.node.name; - this.nestedCollections = - collectionNode.children != null && collectionNode.children.length > 0 - ? collectionNode.children - : null; - } - await this.load( - (c) => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1, - ); - } else { - this.showVaultFilter = true; - this.groupingTitle = this.i18nService.t("allItems"); - await this.load(this.buildFilter()); - } - - if (this.applySavedState && this.state != 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 - BrowserPopupUtils.setContentScrollY(window, this.state.scrollY, { - delay: 0, - containerSelector: this.scrollingContainer, - }); - } - await this.stateService.setBrowserVaultItemsComponentState(null); - }); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (message.successfully) { - window.setTimeout(() => { - // 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.refresh(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // 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.saveState(); - this.broadcasterService.unsubscribe(ComponentId); - } - - selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - super.selectCipher(cipher); - // 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(["/view-cipher"], { - queryParams: { cipherId: cipher.id, collectionId: this.collectionId }, - }); - } - this.preventSelected = false; - }, 200); - } - - selectFolder(folder: FolderView) { - if (folder.id != 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(["/ciphers"], { queryParams: { folderId: folder.id } }); - } - } - - selectCollection(collection: CollectionView) { - // 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(["/ciphers"], { queryParams: { collectionId: collection.id } }); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; - } - - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); - // 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 - BrowserApi.createNewTab(cipher.login.launchUri); - if (BrowserPopupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } - } - - addCipher() { - if (this.deleted) { - return false; - } - super.addCipher(); - // 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(["/add-cipher"], { - queryParams: { - folderId: this.folderId, - type: this.type, - collectionId: this.collectionId, - selectedVault: this.vaultFilter.selectedOrganizationId, - }, - }); - } - - back() { - (window as any).routeDirection = "b"; - this.location.back(); - } - - showGroupings() { - return ( - !this.isSearching() && - ((this.nestedFolders && this.nestedFolders.length) || - (this.nestedCollections && this.nestedCollections.length)) - ); - } - - async changeVaultSelection() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - await this.load(this.buildFilter(), this.deleted); - } - - private buildFilter(): (cipher: CipherView) => boolean { - return (cipher) => { - let cipherPassesFilter = true; - if (this.deleted && cipherPassesFilter) { - cipherPassesFilter = cipher.isDeleted; - } - if (this.type != null && cipherPassesFilter) { - cipherPassesFilter = cipher.type === this.type; - } - if (this.folderId != null && this.folderId != "none" && cipherPassesFilter) { - cipherPassesFilter = cipher.folderId === this.folderId; - } - if (this.noneFolder) { - cipherPassesFilter = cipher.folderId == null; - } - if (this.collectionId != null && cipherPassesFilter) { - cipherPassesFilter = - cipher.collectionIds != null && cipher.collectionIds.indexOf(this.collectionId) > -1; - } - if (this.vaultFilter.selectedOrganizationId != null && cipherPassesFilter) { - cipherPassesFilter = cipher.organizationId === this.vaultFilter.selectedOrganizationId; - } - if (this.vaultFilter.myVaultOnly && cipherPassesFilter) { - cipherPassesFilter = cipher.organizationId === null; - } - return cipherPassesFilter; - }; - } - - private async saveState() { - this.state = { - scrollY: BrowserPopupUtils.getContentScrollY(window, this.scrollingContainer), - searchText: this.searchText, - }; - await this.stateService.setBrowserVaultItemsComponentState(this.state); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-select.component.html b/apps/browser/src/vault/popup/components/vault/vault-select.component.html deleted file mode 100644 index 4f6ce3a11e6..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-select.component.html +++ /dev/null @@ -1,82 +0,0 @@ - -
    - - - - - - -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/vault-select.component.ts b/apps/browser/src/vault/popup/components/vault/vault-select.component.ts deleted file mode 100644 index 3c5061a516f..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-select.component.ts +++ /dev/null @@ -1,227 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { ConnectedPosition, Overlay, OverlayRef } from "@angular/cdk/overlay"; -import { TemplatePortal } from "@angular/cdk/portal"; -import { - Component, - ElementRef, - EventEmitter, - HostListener, - OnDestroy, - OnInit, - Output, - TemplateRef, - ViewChild, - ViewContainerRef, -} from "@angular/core"; -import { - BehaviorSubject, - combineLatest, - concatMap, - map, - merge, - Observable, - Subject, - takeUntil, -} from "rxjs"; - -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -import { VaultFilterService } from "../../../services/vault-filter.service"; - -@Component({ - selector: "app-vault-select", - templateUrl: "vault-select.component.html", - animations: [ - trigger("transformPanel", [ - state( - "void", - style({ - opacity: 0, - }), - ), - transition( - "void => open", - animate( - "100ms linear", - style({ - opacity: 1, - }), - ), - ), - transition("* => void", animate("100ms linear", style({ opacity: 0 }))), - ]), - ], -}) -export class VaultSelectComponent implements OnInit, OnDestroy { - @Output() onVaultSelectionChanged = new EventEmitter(); - - @ViewChild("toggleVaults", { read: ElementRef }) - buttonRef: ElementRef; - @ViewChild("vaultSelectorTemplate", { read: TemplateRef }) templateRef: TemplateRef; - - private _selectedVault = new BehaviorSubject(null); - - isOpen = false; - loaded = false; - organizations$: Observable; - selectedVault$: Observable = this._selectedVault.asObservable(); - - enforcePersonalOwnership = false; - overlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "bottom", - overlayX: "start", - overlayY: "top", - }, - ]; - - private overlayRef: OverlayRef; - private _destroy = new Subject(); - - shouldShow(organizations: Organization[]): boolean { - return ( - (organizations.length > 0 && !this.enforcePersonalOwnership) || - (organizations.length > 1 && this.enforcePersonalOwnership) - ); - } - - constructor( - private vaultFilterService: VaultFilterService, - private i18nService: I18nService, - private overlay: Overlay, - private viewContainerRef: ViewContainerRef, - private platformUtilsService: PlatformUtilsService, - private organizationService: OrganizationService, - private policyService: PolicyService, - ) {} - - @HostListener("document:keydown.escape", ["$event"]) - handleKeyboardEvent(event: KeyboardEvent) { - if (this.isOpen) { - event.preventDefault(); - this.close(); - } - } - - async ngOnInit() { - this.organizations$ = this.organizationService.memberOrganizations$ - .pipe(takeUntil(this._destroy)) - .pipe(map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name")))); - - combineLatest([ - this.organizations$, - this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), - ]) - .pipe( - concatMap(async ([organizations, enforcePersonalOwnership]) => { - this.enforcePersonalOwnership = enforcePersonalOwnership; - - if (this.shouldShow(organizations)) { - if (this.enforcePersonalOwnership && !this.vaultFilterService.vaultFilter.myVaultOnly) { - const firstOrganization = organizations[0]; - this._selectedVault.next(firstOrganization.name); - this.vaultFilterService.setVaultFilter(firstOrganization.id); - } else if (this.vaultFilterService.vaultFilter.myVaultOnly) { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.myVault)); - } else if (this.vaultFilterService.vaultFilter.selectedOrganizationId != null) { - const selectedOrganization = organizations.find( - (o) => o.id === this.vaultFilterService.vaultFilter.selectedOrganizationId, - ); - this._selectedVault.next(selectedOrganization.name); - } else { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.allVaults)); - } - } - }), - ) - .pipe(takeUntil(this._destroy)) - .subscribe(); - - this.loaded = true; - } - - ngOnDestroy(): void { - this._destroy.next(); - this._destroy.complete(); - this._selectedVault.complete(); - } - - openOverlay() { - const viewPortHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); - const positionStrategyBuilder = this.overlay.position(); - - const positionStrategy = positionStrategyBuilder - .flexibleConnectedTo(this.buttonRef.nativeElement) - .withFlexibleDimensions(true) - .withPush(true) - .withViewportMargin(10) - .withGrowAfterOpen(true) - .withPositions(this.overlayPosition); - - this.overlayRef = this.overlay.create({ - hasBackdrop: true, - positionStrategy, - maxHeight: viewPortHeight - 160, - backdropClass: "cdk-overlay-transparent-backdrop", - scrollStrategy: this.overlay.scrollStrategies.close(), - }); - - const templatePortal = new TemplatePortal(this.templateRef, this.viewContainerRef); - this.overlayRef.attach(templatePortal); - this.isOpen = true; - - // Handle closing - merge( - this.overlayRef.outsidePointerEvents(), - this.overlayRef.backdropClick(), - this.overlayRef.detachments(), - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - ).subscribe(() => { - this.close(); - }); - } - - close() { - if (this.overlayRef) { - this.overlayRef.dispose(); - this.overlayRef = undefined; - } - this.isOpen = false; - } - - selectOrganization(organization: Organization) { - if (!organization.enabled) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("disabledOrganizationFilterError"), - ); - } else { - this._selectedVault.next(organization.name); - this.vaultFilterService.setVaultFilter(organization.id); - this.onVaultSelectionChanged.emit(); - this.close(); - } - } - selectAllVaults() { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.allVaults)); - this.vaultFilterService.setVaultFilter(this.vaultFilterService.allVaults); - this.onVaultSelectionChanged.emit(); - this.close(); - } - selectMyVault() { - this._selectedVault.next(this.i18nService.t(this.vaultFilterService.myVault)); - this.vaultFilterService.setVaultFilter(this.vaultFilterService.myVault); - this.onVaultSelectionChanged.emit(); - this.close(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.html b/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.html deleted file mode 100644 index 4fbca28734b..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.html +++ /dev/null @@ -1,98 +0,0 @@ - -

    - {{ "customFields" | i18n }} -

    -
    -
    -
    - {{ field.name }} - {{ field.name }} -
    - {{ field.value || " " }} -
    -
    - {{ field.maskedValue }} - - -
    -
    - - - {{ field.value }} -
    -
    -
    - - {{ "linkedValue" | i18n }} -
    - {{ cipher.linkedFieldI18nKey(field.linkedId) | i18n }} -
    -
    -
    - - - -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.ts b/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.ts deleted file mode 100644 index 249f83c4444..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view-custom-fields.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component } from "@angular/core"; - -import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "@bitwarden/angular/vault/components/view-custom-fields.component"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; - -@Component({ - selector: "app-vault-view-custom-fields", - templateUrl: "view-custom-fields.component.html", -}) -export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { - constructor(eventCollectionService: EventCollectionService) { - super(eventCollectionService); - } -} diff --git a/apps/browser/src/vault/popup/components/vault/view.component.html b/apps/browser/src/vault/popup/components/vault/view.component.html deleted file mode 100644 index 57a5d007d8a..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view.component.html +++ /dev/null @@ -1,719 +0,0 @@ -
    -
    - -
    -

    - {{ "viewItem" | i18n }} -

    -
    - -
    -
    -
    -
    -

    - {{ "itemInformation" | i18n }} -

    -
    -
    - - -
    - -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - {{ "password" | i18n }} -
    - {{ cipher.login.maskedPassword }} -
    -
    -
    -
    -
    - - - - -
    -
    - - -
    -
    -
    - {{ "typePasskey" | i18n }} - {{ fido2CredentialCreationDateValue }} -
    -
    -
    - -
    -
    - {{ "verificationCodeTotp" | i18n }} - {{ totpCodeFormatted }} -
    - -
    - -
    -
    -
    -
    - {{ "verificationCodeTotp" | i18n }} - - - {{ "premiumSubcriptionRequired" | i18n }} - - -
    -
    -
    - -
    -
    - {{ "cardholderName" | i18n }} - {{ cipher.card.cardholderName }} -
    -
    -
    - {{ "number" | i18n }} - {{ - cipher.card.maskedNumber | creditCardNumber: cipher.card.brand - }} - {{ - cipher.card.number | creditCardNumber: cipher.card.brand - }} -
    -
    - - -
    -
    -
    - {{ "brand" | i18n }} - {{ cipher.card.brand }} -
    -
    - {{ "expiration" | i18n }} - {{ cipher.card.expiration }} -
    -
    -
    - {{ "securityCode" | i18n }} - {{ cipher.card.maskedCode }} - {{ cipher.card.code }} -
    -
    - - -
    -
    -
    - -
    -
    - {{ "identityName" | i18n }} - {{ cipher.identity.fullName }} -
    -
    - {{ "username" | i18n }} - {{ cipher.identity.username }} -
    -
    - {{ "company" | i18n }} - {{ cipher.identity.company }} -
    -
    - {{ "ssn" | i18n }} - {{ cipher.identity.ssn }} -
    -
    - {{ "passportNumber" | i18n }} - {{ cipher.identity.passportNumber }} -
    -
    - {{ "licenseNumber" | i18n }} - {{ cipher.identity.licenseNumber }} -
    -
    - {{ "email" | i18n }} - {{ cipher.identity.email }} -
    -
    - {{ "phone" | i18n }} - {{ cipher.identity.phone }} -
    -
    - {{ "address" | i18n }} -
    {{ cipher.identity.address1 }}
    -
    {{ cipher.identity.address2 }}
    -
    {{ cipher.identity.address3 }}
    -
    {{ cipher.identity.fullAddressPart2 }}
    -
    {{ cipher.identity.country }}
    -
    -
    - -
    -
    - - {{ "sshPrivateKey" | i18n }} - -
    -
    -
    - - {{ "sshPublicKey" | i18n }} - {{ cipher.sshKey.publicKey }} -
    -
    - - {{ "sshFingerprint" | i18n }} - {{ cipher.sshKey.keyFingerprint }} -
    -
    -
    -
    -
    -
    -
    -
    - - - - - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -

    - -

    -
    -
    - -
    -
    -
    -
    - -
    -
    -

    - {{ "attachments" | i18n }} -

    -
    - -
    -
    -
    -
    - - - - - - -
    -
    -
    - -
    -
    diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts deleted file mode 100644 index 242cff03c75..00000000000 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ /dev/null @@ -1,443 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe, Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnInit, OnDestroy } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, firstValueFrom, takeUntil, Subscription } from "rxjs"; -import { first, map } from "rxjs/operators"; - -import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { 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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { BrowserFido2UserInterfaceSession } from "../../../../autofill/fido2/services/browser-fido2-user-interface.service"; -import { AutofillService } from "../../../../autofill/services/abstractions/autofill.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data"; -import { closeViewVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window"; - -const BroadcasterSubscriptionId = "ChildViewComponent"; - -export const AUTOFILL_ID = "autofill"; -export const SHOW_AUTOFILL_BUTTON = "show-autofill-button"; -export const COPY_USERNAME_ID = "copy-username"; -export const COPY_PASSWORD_ID = "copy-password"; -export const COPY_VERIFICATION_CODE_ID = "copy-totp"; - -type CopyAction = - | typeof COPY_USERNAME_ID - | typeof COPY_PASSWORD_ID - | typeof COPY_VERIFICATION_CODE_ID; -type LoadAction = typeof AUTOFILL_ID | typeof SHOW_AUTOFILL_BUTTON | CopyAction; - -@Component({ - selector: "app-vault-view", - templateUrl: "view.component.html", -}) -export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy { - showAttachments = true; - pageDetails: any[] = []; - tab: any; - senderTabId?: number; - loadAction?: LoadAction; - private static readonly copyActions = new Set([ - COPY_USERNAME_ID, - COPY_PASSWORD_ID, - COPY_VERIFICATION_CODE_ID, - ]); - uilocation?: "popout" | "popup" | "sidebar" | "tab"; - loadPageDetailsTimeout: number; - inPopout = false; - cipherType = CipherType; - private fido2PopoutSessionData$ = fido2PopoutSessionData$(); - private collectPageDetailsSubscription: Subscription; - - private destroy$ = new Subject(); - - constructor( - cipherService: CipherService, - folderService: FolderService, - totpService: TotpServiceAbstraction, - tokenService: TokenService, - i18nService: I18nService, - keyService: KeyService, - encryptService: EncryptService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - broadcasterService: BroadcasterService, - ngZone: NgZone, - changeDetectorRef: ChangeDetectorRef, - stateService: StateService, - eventCollectionService: EventCollectionService, - private autofillService: AutofillService, - private messagingService: MessagingService, - apiService: ApiService, - passwordRepromptService: PasswordRepromptService, - logService: LogService, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - datePipe: DatePipe, - accountService: AccountService, - billingAccountProfileStateService: BillingAccountProfileStateService, - cipherAuthorizationService: CipherAuthorizationService, - ) { - super( - cipherService, - folderService, - totpService, - tokenService, - i18nService, - keyService, - encryptService, - platformUtilsService, - auditService, - window, - broadcasterService, - ngZone, - changeDetectorRef, - eventCollectionService, - apiService, - passwordRepromptService, - logService, - stateService, - fileDownloadService, - dialogService, - datePipe, - accountService, - billingAccountProfileStateService, - cipherAuthorizationService, - ); - } - - ngOnInit() { - this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((value) => { - this.loadAction = value?.action; - this.senderTabId = parseInt(value?.senderTabId, 10) || undefined; - this.uilocation = value?.uilocation; - }); - - this.inPopout = this.uilocation === "popout" || BrowserPopupUtils.inPopout(window); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } - - if (params.collectionId) { - this.collectionId = params.collectionId; - } - - if (!params.cipherId) { - // 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.close(); - } - - await this.load(); - }); - - super.ngOnInit(); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "tabChanged": - case "windowChanged": - if (this.loadPageDetailsTimeout != null) { - window.clearTimeout(this.loadPageDetailsTimeout); - } - this.loadPageDetailsTimeout = window.setTimeout(() => this.loadPageDetails(), 500); - break; - default: - break; - } - }); - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - super.ngOnDestroy(); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - async load() { - await super.load(); - await this.loadPageDetails(); - await this.handleLoadAction(); - } - - async edit() { - if (this.cipher.isDeleted) { - return false; - } - if (!(await super.edit())) { - 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(["/edit-cipher"], { - queryParams: { - cipherId: this.cipher.id, - type: this.cipher.type, - isNew: false, - collectionId: this.collectionId, - }, - }); - return true; - } - - async clone() { - if (this.cipher.isDeleted) { - return false; - } - - if (!(await super.clone())) { - 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(["/clone-cipher"], { - queryParams: { - cloneMode: true, - cipherId: this.cipher.id, - }, - }); - return true; - } - - async share() { - if (!(await super.share())) { - return false; - } - - if (this.cipher.organizationId == 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(["/share-cipher"], { - replaceUrl: true, - queryParams: { cipherId: this.cipher.id }, - }); - } - return true; - } - - async fillCipher() { - const didAutofill = await this.doAutofill(); - if (didAutofill) { - this.platformUtilsService.showToast("success", null, this.i18nService.t("autoFillSuccess")); - } - - return didAutofill; - } - - async fillCipherAndSave() { - const didAutofill = await this.doAutofill(); - - if (didAutofill) { - if (this.tab == null) { - throw new Error("No tab found."); - } - - if (this.cipher.login.uris == null) { - this.cipher.login.uris = []; - } else { - if (this.cipher.login.uris.some((uri) => uri.uri === this.tab.url)) { - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("autoFillSuccessAndSavedUri"), - ); - return; - } - } - - const loginUri = new LoginUriView(); - loginUri.uri = this.tab.url; - this.cipher.login.uris.push(loginUri); - - try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const cipher: Cipher = await this.cipherService.encrypt(this.cipher, activeUserId); - await this.cipherService.updateWithServer(cipher); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("autoFillSuccessAndSavedUri"), - ); - this.messagingService.send("editedCipher"); - } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); - } - } - } - - async restore() { - if (!this.cipher.isDeleted) { - return false; - } - if (await super.restore()) { - // 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.close(); - return true; - } - return false; - } - - async delete() { - if (await super.delete()) { - this.messagingService.send("deletedCipher"); - // 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.close(); - return true; - } - return false; - } - - async close() { - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - if (this.inPopout && sessionData.isFido2Session) { - BrowserFido2UserInterfaceSession.abortPopout(sessionData.sessionId); - return; - } - - if ( - BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.viewVaultItem) && - this.senderTabId - ) { - // 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 - BrowserApi.focusTab(this.senderTabId); - // 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 - closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${this.cipher.id}`); - return; - } - - this.location.back(); - } - - private async loadPageDetails() { - this.collectPageDetailsSubscription?.unsubscribe(); - this.pageDetails = []; - this.tab = this.senderTabId - ? await BrowserApi.getTab(this.senderTabId) - : await BrowserApi.getTabFromCurrentWindow(); - - if (!this.tab) { - return; - } - - this.collectPageDetailsSubscription = this.autofillService - .collectPageDetailsFromTab$(this.tab) - .pipe(takeUntil(this.destroy$)) - .subscribe((pageDetails) => (this.pageDetails = pageDetails)); - } - - private async doAutofill() { - const originalTabURL = this.tab.url?.length && new URL(this.tab.url); - - if (!(await this.promptPassword())) { - return false; - } - - const currentTabURL = this.tab.url?.length && new URL(this.tab.url); - - const originalTabHostPath = - originalTabURL && `${originalTabURL.origin}${originalTabURL.pathname}`; - const currentTabHostPath = currentTabURL && `${currentTabURL.origin}${currentTabURL.pathname}`; - - const tabUrlChanged = originalTabHostPath !== currentTabHostPath; - - if (this.pageDetails == null || this.pageDetails.length === 0 || tabUrlChanged) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - return false; - } - - try { - this.totpCode = await this.autofillService.doAutoFill({ - tab: this.tab, - cipher: this.cipher, - pageDetails: this.pageDetails, - doc: window.document, - fillNewPassword: true, - allowTotpAutofill: true, - }); - if (this.totpCode != null) { - this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); - } - } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - this.changeDetectorRef.detectChanges(); - return false; - } - - return true; - } - - private async handleLoadAction() { - if (!this.loadAction || this.loadAction === SHOW_AUTOFILL_BUTTON) { - return; - } - - let loadActionSuccess = false; - if (this.loadAction === AUTOFILL_ID) { - loadActionSuccess = await this.fillCipher(); - } - - if (ViewComponent.copyActions.has(this.loadAction)) { - const { username, password } = this.cipher.login; - const copyParams: Record> = { - [COPY_USERNAME_ID]: { value: username, type: "username", name: "Username" }, - [COPY_PASSWORD_ID]: { value: password, type: "password", name: "Password" }, - [COPY_VERIFICATION_CODE_ID]: { - value: this.totpCode, - type: "verificationCodeTotp", - name: "TOTP", - }, - }; - const { value, type, name } = copyParams[this.loadAction as CopyAction]; - loadActionSuccess = await this.copy(value, type, name); - } - - if (this.inPopout) { - setTimeout(() => this.close(), loadActionSuccess ? 1000 : 0); - } - } -} diff --git a/apps/browser/src/vault/popup/settings/appearance.component.html b/apps/browser/src/vault/popup/settings/appearance.component.html deleted file mode 100644 index a431fc72a1f..00000000000 --- a/apps/browser/src/vault/popup/settings/appearance.component.html +++ /dev/null @@ -1,80 +0,0 @@ -
    -
    - -
    -

    - {{ "appearance" | i18n }} -

    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/settings/appearance.component.ts b/apps/browser/src/vault/popup/settings/appearance.component.ts deleted file mode 100644 index e6d03c5b01f..00000000000 --- a/apps/browser/src/vault/popup/settings/appearance.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { ThemeType } from "@bitwarden/common/platform/enums"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; - -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "vault-appearance", - templateUrl: "appearance.component.html", -}) -export class AppearanceComponent implements OnInit { - enableFavicon = false; - enableBadgeCounter = true; - theme: ThemeType; - themeOptions: any[]; - accountSwitcherEnabled = false; - enableRoutingAnimation: boolean; - - constructor( - private messagingService: MessagingService, - private domainSettingsService: DomainSettingsService, - private badgeSettingsService: BadgeSettingsServiceAbstraction, - i18nService: I18nService, - private themeStateService: ThemeStateService, - private animationControlService: AnimationControlService, - ) { - this.themeOptions = [ - { name: i18nService.t("default"), value: ThemeType.System }, - { name: i18nService.t("light"), value: ThemeType.Light }, - { name: i18nService.t("dark"), value: ThemeType.Dark }, - { name: "Nord", value: ThemeType.Nord }, - { name: i18nService.t("solarizedDark"), value: ThemeType.SolarizedDark }, - ]; - - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - this.enableRoutingAnimation = await firstValueFrom( - this.animationControlService.enableRoutingAnimation$, - ); - - this.enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$); - - this.enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$); - - this.theme = await firstValueFrom(this.themeStateService.selectedTheme$); - } - - async updateRoutingAnimation() { - await this.animationControlService.setEnableRoutingAnimation(this.enableRoutingAnimation); - } - - async updateFavicon() { - await this.domainSettingsService.setShowFavicons(this.enableFavicon); - } - - async updateBadgeCounter() { - await this.badgeSettingsService.setEnableBadgeCounter(this.enableBadgeCounter); - this.messagingService.send("bgUpdateContextMenu"); - } - - async saveTheme() { - await this.themeStateService.setSelectedTheme(this.theme); - } -} diff --git a/apps/browser/src/vault/popup/settings/folder-add-edit.component.html b/apps/browser/src/vault/popup/settings/folder-add-edit.component.html deleted file mode 100644 index 14393b83ddc..00000000000 --- a/apps/browser/src/vault/popup/settings/folder-add-edit.component.html +++ /dev/null @@ -1,49 +0,0 @@ -
    -
    -
    - -
    -

    - {{ title }} -

    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts b/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts deleted file mode 100644 index 122922a4d2d..00000000000 --- a/apps/browser/src/vault/popup/settings/folder-add-edit.component.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { DialogService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Component({ - selector: "app-folder-add-edit", - templateUrl: "folder-add-edit.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class FolderAddEditComponent extends BaseFolderAddEditComponent implements OnInit { - constructor( - folderService: FolderService, - folderApiService: FolderApiServiceAbstraction, - accountService: AccountService, - keyService: KeyService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - private router: Router, - private route: ActivatedRoute, - logService: LogService, - dialogService: DialogService, - formBuilder: FormBuilder, - ) { - super( - folderService, - folderApiService, - accountService, - keyService, - i18nService, - platformUtilsService, - logService, - dialogService, - formBuilder, - ); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.folderId) { - this.folderId = params.folderId; - } - await this.init(); - }); - } - - async submit(): Promise { - if (await super.submit()) { - // 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(["/folders"]); - return true; - } - - return false; - } - - async delete(): Promise { - const confirmed = await super.delete(); - 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 - this.router.navigate(["/folders"]); - } - return confirmed; - } -} diff --git a/apps/browser/src/vault/popup/settings/folders.component.html b/apps/browser/src/vault/popup/settings/folders.component.html deleted file mode 100644 index 47cdb0188d2..00000000000 --- a/apps/browser/src/vault/popup/settings/folders.component.html +++ /dev/null @@ -1,38 +0,0 @@ -
    -
    - -
    -

    - {{ "folders" | i18n }} -

    -
    - -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    -

    {{ "noFolders" | i18n }}

    -
    -
    -
    diff --git a/apps/browser/src/vault/popup/settings/folders.component.ts b/apps/browser/src/vault/popup/settings/folders.component.ts deleted file mode 100644 index 1e3f182b43d..00000000000 --- a/apps/browser/src/vault/popup/settings/folders.component.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; -import { filter, map, Observable, switchMap } from "rxjs"; - -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -@Component({ - selector: "app-folders", - templateUrl: "folders.component.html", -}) -export class FoldersComponent { - folders$: Observable; - - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - - constructor( - private folderService: FolderService, - private router: Router, - private accountService: AccountService, - ) { - this.folders$ = this.activeUserId$.pipe( - filter((userId): userId is UserId => userId != null), - switchMap((userId) => this.folderService.folderViews$(userId)), - map((folders) => { - // Remove the last folder, which is the "no folder" option folder - if (folders.length > 0) { - return folders.slice(0, folders.length - 1); - } - return folders; - }), - ); - } - - folderSelected(folder: FolderView) { - // 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(["/edit-folder"], { queryParams: { folderId: folder.id } }); - } - - addFolder() { - // 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(["/add-folder"]); - } -} diff --git a/apps/browser/src/vault/popup/settings/sync.component.html b/apps/browser/src/vault/popup/settings/sync.component.html deleted file mode 100644 index 6d0a1c31a8b..00000000000 --- a/apps/browser/src/vault/popup/settings/sync.component.html +++ /dev/null @@ -1,35 +0,0 @@ -
    -
    - -
    -

    - {{ "sync" | i18n }} -

    -
    -
    -
    -
    - -

    - {{ "lastSync" | i18n }} {{ lastSync }} -

    -
    -
    diff --git a/apps/browser/src/vault/popup/settings/sync.component.ts b/apps/browser/src/vault/popup/settings/sync.component.ts deleted file mode 100644 index 6585a71d94b..00000000000 --- a/apps/browser/src/vault/popup/settings/sync.component.ts +++ /dev/null @@ -1,46 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; - -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; - -@Component({ - selector: "app-sync", - templateUrl: "sync.component.html", -}) -export class SyncComponent implements OnInit { - lastSync = "--"; - syncPromise: Promise; - - constructor( - private syncService: SyncService, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - ) {} - - async ngOnInit() { - await this.setLastSync(); - } - - async sync() { - this.syncPromise = this.syncService.fullSync(true); - const success = await this.syncPromise; - if (success) { - await this.setLastSync(); - this.platformUtilsService.showToast("success", null, this.i18nService.t("syncingComplete")); - } else { - this.platformUtilsService.showToast("error", null, this.i18nService.t("syncingFailed")); - } - } - - async setLastSync() { - const last = await this.syncService.getLastSync(); - if (last != null) { - this.lastSync = last.toLocaleDateString() + " " + last.toLocaleTimeString(); - } else { - this.lastSync = this.i18nService.t("never"); - } - } -} diff --git a/apps/browser/src/vault/popup/settings/vault-settings.component.html b/apps/browser/src/vault/popup/settings/vault-settings.component.html deleted file mode 100644 index 4928720e46e..00000000000 --- a/apps/browser/src/vault/popup/settings/vault-settings.component.html +++ /dev/null @@ -1,56 +0,0 @@ - -
    - -
    -

    - {{ "vault" | i18n }} -

    -
    - -
    -
    -
    -
    -
    - - - - -
    -
    -
    diff --git a/apps/browser/src/vault/popup/settings/vault-settings.component.ts b/apps/browser/src/vault/popup/settings/vault-settings.component.ts deleted file mode 100644 index a12f6d1d5be..00000000000 --- a/apps/browser/src/vault/popup/settings/vault-settings.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; - -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; - -import { BrowserApi } from "../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; - -@Component({ - selector: "vault-settings", - templateUrl: "vault-settings.component.html", -}) -export class VaultSettingsComponent { - constructor( - public messagingService: MessagingService, - private router: Router, - ) {} - - async import() { - await this.router.navigate(["/import"]); - if (await BrowserApi.isPopupOpen()) { - await BrowserPopupUtils.openCurrentPagePopout(window); - } - } -} From 066773e9834e679afe308ef1b215b9c280f3dd1b Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Mon, 6 Jan 2025 16:05:29 -0500 Subject: [PATCH 071/270] [PM-16524] move integration page shared components into admin console ownership (#12664) * move integrations shared components to AC ownership * fix stories --- .../integrations/integrations.component.ts | 4 +++- .../shared/components/integrations/index.ts | 4 ++++ .../integration-card.component.html | 0 .../integration-card.component.spec.ts | 0 .../integration-card.component.ts | 2 +- .../integration-card.stories.ts | 17 ++++++--------- .../integration-grid.component.html | 0 .../integration-grid.component.spec.ts | 0 .../integration-grid.component.ts | 2 +- .../integration-grid.stories.ts | 21 +++++++------------ .../integrations/integrations.pipe.ts | 0 .../shared/components/integrations/models.ts | 0 apps/web/src/app/shared/components/index.ts | 4 ---- apps/web/src/app/shared/index.ts | 1 - .../integrations.component.spec.ts | 7 +++---- .../integrations/integrations.component.ts | 2 +- .../integrations/integrations.module.ts | 6 ++---- 17 files changed, 28 insertions(+), 42 deletions(-) create mode 100644 apps/web/src/app/admin-console/organizations/shared/components/integrations/index.ts rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-card/integration-card.component.html (100%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-card/integration-card.component.spec.ts (100%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-card/integration-card.component.ts (97%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-card/integration-card.stories.ts (74%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-grid/integration-grid.component.html (100%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-grid/integration-grid.component.spec.ts (100%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-grid/integration-grid.component.ts (91%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integration-grid/integration-grid.stories.ts (75%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/integrations.pipe.ts (100%) rename apps/web/src/app/{ => admin-console/organizations}/shared/components/integrations/models.ts (100%) delete mode 100644 apps/web/src/app/shared/components/index.ts diff --git a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts index d7ab6a6f617..e3edb41de76 100644 --- a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts +++ b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts @@ -9,9 +9,11 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { IntegrationType } from "@bitwarden/common/enums"; import { HeaderModule } from "../../../layouts/header/header.module"; -import { FilterIntegrationsPipe, IntegrationGridComponent, Integration } from "../../../shared/"; import { SharedModule } from "../../../shared/shared.module"; import { SharedOrganizationModule } from "../shared"; +import { IntegrationGridComponent } from "../shared/components/integrations/integration-grid/integration-grid.component"; +import { FilterIntegrationsPipe } from "../shared/components/integrations/integrations.pipe"; +import { Integration } from "../shared/components/integrations/models"; @Component({ selector: "ac-integrations", diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/index.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/index.ts new file mode 100644 index 00000000000..c8fe9d32652 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/index.ts @@ -0,0 +1,4 @@ +export * from "./integrations.pipe"; +export * from "./integration-card/integration-card.component"; +export * from "./integration-grid/integration-grid.component"; +export * from "./models"; diff --git a/apps/web/src/app/shared/components/integrations/integration-card/integration-card.component.html b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.html similarity index 100% rename from apps/web/src/app/shared/components/integrations/integration-card/integration-card.component.html rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.html diff --git a/apps/web/src/app/shared/components/integrations/integration-card/integration-card.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts similarity index 100% rename from apps/web/src/app/shared/components/integrations/integration-card/integration-card.component.spec.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts diff --git a/apps/web/src/app/shared/components/integrations/integration-card/integration-card.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts similarity index 97% rename from apps/web/src/app/shared/components/integrations/integration-card/integration-card.component.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts index 5e47c1e0b31..681b93413e8 100644 --- a/apps/web/src/app/shared/components/integrations/integration-card/integration-card.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts @@ -15,7 +15,7 @@ import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-t import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { SharedModule } from "../../../shared.module"; +import { SharedModule } from "../../../../../../shared/shared.module"; @Component({ selector: "app-integration-card", diff --git a/apps/web/src/app/shared/components/integrations/integration-card/integration-card.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.stories.ts similarity index 74% rename from apps/web/src/app/shared/components/integrations/integration-card/integration-card.stories.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.stories.ts index 1d1e229740f..256bfd3d827 100644 --- a/apps/web/src/app/shared/components/integrations/integration-card/integration-card.stories.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.stories.ts @@ -1,13 +1,12 @@ -import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { importProvidersFrom } from "@angular/core"; +import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; import { of } from "rxjs"; import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { I18nMockService } from "@bitwarden/components"; -import { SharedModule } from "../../../shared.module"; +import { PreloadedEnglishI18nModule } from "../../../../../../core/tests"; import { IntegrationCardComponent } from "./integration-card.component"; @@ -17,15 +16,11 @@ export default { title: "Web/Integration Layout/Integration Card", component: IntegrationCardComponent, decorators: [ + applicationConfig({ + providers: [importProvidersFrom(PreloadedEnglishI18nModule)], + }), moduleMetadata({ - imports: [SharedModule], providers: [ - { - provide: I18nService, - useFactory: () => { - return new I18nMockService({}); - }, - }, { provide: ThemeStateService, useClass: MockThemeService, diff --git a/apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.component.html b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.html similarity index 100% rename from apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.component.html rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.html diff --git a/apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts similarity index 100% rename from apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.component.spec.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts diff --git a/apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts similarity index 91% rename from apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.component.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts index 1ec3f0d8d48..2e3158f9894 100644 --- a/apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts @@ -4,7 +4,7 @@ import { Component, Input } from "@angular/core"; import { IntegrationType } from "@bitwarden/common/enums"; -import { SharedModule } from "../../../shared.module"; +import { SharedModule } from "../../../../../../shared/shared.module"; import { IntegrationCardComponent } from "../integration-card/integration-card.component"; import { Integration } from "../models"; diff --git a/apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.stories.ts similarity index 75% rename from apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.stories.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.stories.ts index 2ec0bccec3d..b6580af2881 100644 --- a/apps/web/src/app/shared/components/integrations/integration-grid/integration-grid.stories.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.stories.ts @@ -1,14 +1,13 @@ -import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { importProvidersFrom } from "@angular/core"; +import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; import { of } from "rxjs"; import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; import { IntegrationType } from "@bitwarden/common/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { I18nMockService } from "@bitwarden/components"; -import { SharedModule } from "../../../shared.module"; +import { PreloadedEnglishI18nModule } from "../../../../../../core/tests"; import { IntegrationCardComponent } from "../integration-card/integration-card.component"; import { IntegrationGridComponent } from "../integration-grid/integration-grid.component"; @@ -18,18 +17,12 @@ export default { title: "Web/Integration Layout/Integration Grid", component: IntegrationGridComponent, decorators: [ + applicationConfig({ + providers: [importProvidersFrom(PreloadedEnglishI18nModule)], + }), moduleMetadata({ - imports: [IntegrationCardComponent, SharedModule], + imports: [IntegrationCardComponent], providers: [ - { - provide: I18nService, - useFactory: () => { - return new I18nMockService({ - integrationCardAriaLabel: "Go to integration", - integrationCardTooltip: "Go to integration", - }); - }, - }, { provide: ThemeStateService, useClass: MockThemeService, diff --git a/apps/web/src/app/shared/components/integrations/integrations.pipe.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts similarity index 100% rename from apps/web/src/app/shared/components/integrations/integrations.pipe.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts diff --git a/apps/web/src/app/shared/components/integrations/models.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/models.ts similarity index 100% rename from apps/web/src/app/shared/components/integrations/models.ts rename to apps/web/src/app/admin-console/organizations/shared/components/integrations/models.ts diff --git a/apps/web/src/app/shared/components/index.ts b/apps/web/src/app/shared/components/index.ts deleted file mode 100644 index 5745a7827ff..00000000000 --- a/apps/web/src/app/shared/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./integrations/integration-card/integration-card.component"; -export * from "./integrations/integration-grid/integration-grid.component"; -export * from "./integrations/integrations.pipe"; -export * from "./integrations/models"; diff --git a/apps/web/src/app/shared/index.ts b/apps/web/src/app/shared/index.ts index f57648c0e40..7defcdedfda 100644 --- a/apps/web/src/app/shared/index.ts +++ b/apps/web/src/app/shared/index.ts @@ -1,3 +1,2 @@ export * from "./shared.module"; export * from "./loose-components.module"; -export * from "./components/index"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts index e4a65f7ddd8..5d626da9364 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -5,10 +5,9 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; import { SharedModule } from "@bitwarden/components/src/shared"; -import { - IntegrationCardComponent, - IntegrationGridComponent, -} from "@bitwarden/web-vault/app/shared"; +import { IntegrationCardComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component"; +import { IntegrationGridComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component"; +import {} from "@bitwarden/web-vault/app/shared"; import { SYSTEM_THEME_OBSERVABLE } from "../../../../../../libs/angular/src/services/injection-tokens"; import { I18nService } from "../../../../../../libs/common/src/platform/abstractions/i18n.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts index af15c2c8b6d..cdae129de4f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { IntegrationType } from "@bitwarden/common/enums"; -import { Integration } from "@bitwarden/web-vault/app/shared"; +import { Integration } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/models"; @Component({ selector: "sm-integrations", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts index b79892f5ed6..eee426e3b07 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts @@ -1,9 +1,7 @@ import { NgModule } from "@angular/core"; -import { - IntegrationCardComponent, - IntegrationGridComponent, -} from "@bitwarden/web-vault/app/shared"; +import { IntegrationCardComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component"; +import { IntegrationGridComponent } from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component"; import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; From 5a46991e4e5e21b84ad0519391204ecddb0b3266 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Mon, 6 Jan 2025 16:08:29 -0500 Subject: [PATCH 072/270] [PM-16696] New Device Verification Notice Learn More (#12715) * add learn more link to new device verification notification page one --- ...new-device-verification-notice-page-one.component.html | 8 ++++++++ .../new-device-verification-notice-page-one.component.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html b/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html index 316df3aed17..ddff560fd00 100644 --- a/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html +++ b/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html @@ -1,6 +1,14 @@

    {{ "newDeviceVerificationNoticeContentPage1" | i18n }} + + {{ "learnMore" | i18n }}. +

    Date: Mon, 6 Jan 2025 13:52:42 -0800 Subject: [PATCH 073/270] [PM-13365] - don't display totp capture when in popout (#12645) * don't display totp capture when in popout * add canCaptureTotp method * dry up logic * add unit tests * fix failing tests * add missing mock to cipher-form story --- .../services/browser-totp-capture.service.spec.ts | 15 +++++++++++++++ .../services/browser-totp-capture.service.ts | 5 +++++ .../abstractions/totp-capture.service.ts | 5 +++++ libs/vault/src/cipher-form/cipher-form.stories.ts | 1 + .../login-details-section.component.spec.ts | 4 +++- .../login-details-section.component.ts | 8 ++++++-- 6 files changed, 35 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts b/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts index 2c9afacffd7..2b309e8f817 100644 --- a/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts +++ b/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts @@ -2,6 +2,7 @@ import { TestBed } from "@angular/core/testing"; import qrcodeParser from "qrcode-parser"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { BrowserTotpCaptureService } from "./browser-totp-capture.service"; @@ -13,12 +14,14 @@ describe("BrowserTotpCaptureService", () => { let testBed: TestBed; let service: BrowserTotpCaptureService; let mockCaptureVisibleTab: jest.SpyInstance; + let mockBrowserPopupUtilsInPopout: jest.SpyInstance; const validTotpUrl = "otpauth://totp/label?secret=123"; beforeEach(() => { mockCaptureVisibleTab = jest.spyOn(BrowserApi, "captureVisibleTab"); mockCaptureVisibleTab.mockResolvedValue("screenshot"); + mockBrowserPopupUtilsInPopout = jest.spyOn(BrowserPopupUtils, "inPopout"); testBed = TestBed.configureTestingModule({ providers: [BrowserTotpCaptureService], @@ -66,4 +69,16 @@ describe("BrowserTotpCaptureService", () => { expect(result).toBeNull(); }); + + describe("canCaptureTotp", () => { + it("should return true when not in a popout window", () => { + mockBrowserPopupUtilsInPopout.mockReturnValue(false); + expect(service.canCaptureTotp({} as Window)).toBe(true); + }); + + it("should return false when in a popout window", () => { + mockBrowserPopupUtilsInPopout.mockReturnValue(true); + expect(service.canCaptureTotp({} as Window)).toBe(false); + }); + }); }); diff --git a/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts b/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts index 3f8ba61ed36..ac73b271c84 100644 --- a/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts +++ b/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts @@ -4,6 +4,7 @@ import qrcodeParser from "qrcode-parser"; import { TotpCaptureService } from "@bitwarden/vault"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; /** * Implementation of TotpCaptureService for the browser which captures the @@ -20,4 +21,8 @@ export class BrowserTotpCaptureService implements TotpCaptureService { } return null; } + + canCaptureTotp(window: Window) { + return !BrowserPopupUtils.inPopout(window); + } } diff --git a/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts b/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts index d6d95565869..72bbb0da12c 100644 --- a/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts +++ b/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts @@ -6,4 +6,9 @@ export abstract class TotpCaptureService { * Captures a TOTP secret and returns it as a string. Returns null if no TOTP secret was found. */ abstract captureTotpSecret(): Promise; + /** + * Returns whether the TOTP secret can be captured from the current tab. + * Only available in the browser extension and when not in a popout window. + */ + abstract canCaptureTotp(window: Window): boolean; } diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 1d44e4542bc..72c4acb23cd 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -152,6 +152,7 @@ export default { provide: TotpCaptureService, useValue: { captureTotpSecret: () => Promise.resolve("some-value"), + canCaptureTotp: () => true, }, }, { diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts index 232a4b2d27b..182427f7f42 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts @@ -434,6 +434,7 @@ describe("LoginDetailsSectionComponent", () => { }); it("should call captureTotp when the capture totp button is clicked", fakeAsync(() => { + jest.spyOn(component, "canCaptureTotp", "get").mockReturnValue(true); component.captureTotp = jest.fn(); fixture.detectChanges(); @@ -445,7 +446,8 @@ describe("LoginDetailsSectionComponent", () => { })); describe("canCaptureTotp", () => { - it("should return true when totpCaptureService is present and totp is editable", () => { + it("should return true when totpCaptureService is present and totpCaptureService.canCaptureTotp is true and totp is editable", () => { + jest.spyOn(component, "canCaptureTotp", "get").mockReturnValue(true); component.loginDetailsForm.controls.totp.enable(); expect(component.canCaptureTotp).toBe(true); }); diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts index 0a6772e5ef1..2296932aca3 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts @@ -65,10 +65,14 @@ export class LoginDetailsSectionComponent implements OnInit { newPasswordGenerated: boolean; /** - * Whether the TOTP field can be captured from the current tab. Only available in the browser extension. + * Whether the TOTP field can be captured from the current tab. Only available in the browser extension and + * when not in a popout window. */ get canCaptureTotp() { - return this.totpCaptureService != null && this.loginDetailsForm.controls.totp.enabled; + return ( + !!this.totpCaptureService?.canCaptureTotp(window) && + this.loginDetailsForm.controls.totp.enabled + ); } private datePipe = inject(DatePipe); From 15faf52f57a0e0f8e0cb2b48e91216f6b1045907 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Mon, 6 Jan 2025 17:10:34 -0500 Subject: [PATCH 074/270] [PM-13115] Allow users to disable extension content script injections by domain (#11826) * add disabledInteractionsUris state to the domain settings service * add routes and ui for user disabledInteractionsUris state management * use disabled URIs service state as a preemptive conditon to injecting content scripts * move disabled domains navigation button from account security settings to autofill settings * update disabled domain terminology to blocked domain terminology * update copy * handle blocked domains initializing with null value * add dismissable banner to the vault view when the active autofill tab is on the blocked domains list * add autofill blocked domain indicators to autofill suggestions section header * add BlockBrowserInjectionsByDomain feature flag and put feature behind it * update router config to new style * update tests and cleanup * use full-width-notice slot for domain script injection blocked banner * convert thrown error on content script injection block to a warning and early return * simplify and enspeeden state resolution for blockedInteractionsUris * refactor feature flag state fetching and update tests * document domain settings service * remove vault component presentational updates --- apps/browser/src/_locales/en/messages.json | 15 ++ .../background/overlay.background.spec.ts | 8 +- .../overlay.background.deprecated.spec.ts | 6 +- .../popup/settings/autofill-v1.component.html | 12 + .../popup/settings/autofill-v1.component.ts | 5 + .../popup/settings/autofill.component.html | 6 + .../popup/settings/autofill.component.ts | 7 +- .../settings/blocked-domains.component.html | 66 ++++++ .../settings/blocked-domains.component.ts | 208 ++++++++++++++++++ .../settings/excluded-domains.component.ts | 37 ++-- .../services/autofill.service.spec.ts | 13 +- .../browser/src/background/main.background.ts | 7 +- .../browser-script-injector.service.spec.ts | 102 ++++++++- .../browser-script-injector.service.ts | 28 +++ apps/browser/src/popup/app-routing.module.ts | 7 + .../src/popup/services/services.module.ts | 4 +- .../fileless-importer.background.spec.ts | 34 +-- .../service-container/service-container.ts | 43 ++-- .../src/services/jslib-services.module.ts | 10 +- .../services/domain-settings.service.spec.ts | 9 +- .../services/domain-settings.service.ts | 69 +++++- libs/common/src/enums/feature-flag.enum.ts | 2 + .../src/models/domain/domain-service.ts | 2 +- 23 files changed, 623 insertions(+), 77 deletions(-) create mode 100644 apps/browser/src/autofill/popup/settings/blocked-domains.component.html create mode 100644 apps/browser/src/autofill/popup/settings/blocked-domains.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 06f11406b68..85937b63304 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index e7b72b72c9b..512a9ff4c2a 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy, mockReset } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -14,6 +14,7 @@ import { } from "@bitwarden/common/autofill/services/domain-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService, Region, @@ -93,6 +94,7 @@ describe("OverlayBackground", () => { let logService: MockProxy; let cipherService: MockProxy; let autofillService: MockProxy; + let configService: MockProxy; let activeAccountStatusMock$: BehaviorSubject; let authService: MockProxy; let environmentMock$: BehaviorSubject; @@ -149,11 +151,13 @@ describe("OverlayBackground", () => { } beforeEach(() => { + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(true)); accountService = mockAccountServiceWith(mockUserId); fakeStateProvider = new FakeStateProvider(accountService); showFaviconsMock$ = new BehaviorSubject(true); neverDomainsMock$ = new BehaviorSubject({}); - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); domainSettingsService.showFavicons$ = showFaviconsMock$; domainSettingsService.neverDomains$ = neverDomainsMock$; logService = mock(); diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts index 3adaf9e276c..2c22097f3d0 100644 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts +++ b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts @@ -12,6 +12,7 @@ import { DefaultDomainSettingsService, DomainSettingsService, } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService, Region, @@ -61,6 +62,7 @@ describe("OverlayBackground", () => { let overlayBackground: LegacyOverlayBackground; const cipherService = mock(); const autofillService = mock(); + let configService: MockProxy; let activeAccountStatusMock$: BehaviorSubject; let authService: MockProxy; @@ -92,7 +94,9 @@ describe("OverlayBackground", () => { }; beforeEach(() => { - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(true)); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html index 530519e88f1..1c16ee1fe12 100644 --- a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html @@ -255,4 +255,16 @@ {{ "showIdentitiesCurrentTabDesc" | i18n }}
    +
    +
    + +
    +
    diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts b/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts index 085ccba7e1e..9f015d990e9 100644 --- a/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts @@ -36,6 +36,7 @@ export class AutofillV1Component implements OnInit { protected autoFillOverlayVisibilityOptions: any[]; protected disablePasswordManagerLink: string; protected inlineMenuPositioningImprovementsEnabled: boolean = false; + protected blockBrowserInjectionsByDomainEnabled: boolean = false; protected showInlineMenuIdentities: boolean = true; protected showInlineMenuCards: boolean = true; inlineMenuIsEnabled: boolean = false; @@ -120,6 +121,10 @@ export class AutofillV1Component implements OnInit { FeatureFlag.InlineMenuPositioningImprovements, ); + this.blockBrowserInjectionsByDomainEnabled = await this.configService.getFeatureFlag( + FeatureFlag.BlockBrowserInjectionsByDomain, + ); + this.inlineMenuIsEnabled = this.isInlineMenuEnabled(); this.showInlineMenuIdentities = diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index e8882cf7bbb..eeae0a85e3f 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -282,5 +282,11 @@
    + + + {{ "blockedDomains" | i18n }} + + + diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index da997f550b3..884503fa360 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -49,7 +49,6 @@ import { import { BrowserApi } from "../../../platform/browser/browser-api"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -67,7 +66,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co JslibModule, LinkModule, PopOutComponent, - PopupFooterComponent, PopupHeaderComponent, PopupPageComponent, RouterModule, @@ -87,6 +85,7 @@ export class AutofillComponent implements OnInit { protected inlineMenuVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.OnFieldFocus; protected inlineMenuPositioningImprovementsEnabled: boolean = false; + protected blockBrowserInjectionsByDomainEnabled: boolean = false; protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown; protected disablePasswordManagerURI: DisablePasswordManagerUri = DisablePasswordManagerUris.Unknown; @@ -164,6 +163,10 @@ export class AutofillComponent implements OnInit { FeatureFlag.InlineMenuPositioningImprovements, ); + this.blockBrowserInjectionsByDomainEnabled = await this.configService.getFeatureFlag( + FeatureFlag.BlockBrowserInjectionsByDomain, + ); + this.showInlineMenuIdentities = this.inlineMenuPositioningImprovementsEnabled && (await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$)); diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.html b/apps/browser/src/autofill/popup/settings/blocked-domains.component.html new file mode 100644 index 00000000000..bf5f40f2b90 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.html @@ -0,0 +1,66 @@ + + + + + + + +
    +

    {{ "blockedDomainsDesc" | i18n }}

    + + +

    {{ "domainsTitle" | i18n }}

    + {{ blockedDomainsState?.length || 0 }} +
    + + + + + {{ + "websiteItemLabel" | i18n: i + 1 + }} + +
    {{ domain }}
    +
    + +
    +
    + +
    +
    + + + +
    diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts new file mode 100644 index 00000000000..461f62da6dc --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts @@ -0,0 +1,208 @@ +import { CommonModule } from "@angular/common"; +import { + QueryList, + Component, + ElementRef, + OnDestroy, + AfterViewInit, + ViewChildren, +} from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + ButtonModule, + CardComponent, + FormFieldModule, + IconButtonModule, + ItemModule, + LinkModule, + SectionComponent, + SectionHeaderComponent, + ToastService, + TypographyModule, +} from "@bitwarden/components"; + +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +@Component({ + selector: "app-blocked-domains", + templateUrl: "blocked-domains.component.html", + standalone: true, + imports: [ + ButtonModule, + CardComponent, + CommonModule, + FormFieldModule, + FormsModule, + IconButtonModule, + ItemModule, + JslibModule, + LinkModule, + PopOutComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopupPageComponent, + RouterModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ], +}) +export class BlockedDomainsComponent implements AfterViewInit, OnDestroy { + @ViewChildren("uriInput") uriInputElements: QueryList> = + new QueryList(); + + dataIsPristine = true; + isLoading = false; + blockedDomainsState: string[] = []; + storedBlockedDomains: string[] = []; + // How many fields should be non-editable before editable fields + fieldsEditThreshold: number = 0; + + private destroy$ = new Subject(); + + constructor( + private domainSettingsService: DomainSettingsService, + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + async ngAfterViewInit() { + this.domainSettingsService.blockedInteractionsUris$ + .pipe(takeUntil(this.destroy$)) + .subscribe((neverDomains: NeverDomains) => this.handleStateUpdate(neverDomains)); + + this.uriInputElements.changes.pipe(takeUntil(this.destroy$)).subscribe(({ last }) => { + this.focusNewUriInput(last); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + handleStateUpdate(neverDomains: NeverDomains) { + if (neverDomains) { + this.storedBlockedDomains = Object.keys(neverDomains); + } + + this.blockedDomainsState = [...this.storedBlockedDomains]; + + // Do not allow the first x (pre-existing) fields to be edited + this.fieldsEditThreshold = this.storedBlockedDomains.length; + + this.dataIsPristine = true; + this.isLoading = false; + } + + focusNewUriInput(elementRef: ElementRef) { + if (elementRef?.nativeElement) { + elementRef.nativeElement.focus(); + } + } + + async addNewDomain() { + // add empty field to the Domains list interface + this.blockedDomainsState.push(""); + + await this.fieldChange(); + } + + async removeDomain(i: number) { + this.blockedDomainsState.splice(i, 1); + + // If a pre-existing field was dropped, lower the edit threshold + if (i < this.fieldsEditThreshold) { + this.fieldsEditThreshold--; + } + + await this.fieldChange(); + } + + async fieldChange() { + if (this.dataIsPristine) { + this.dataIsPristine = false; + } + } + + async saveChanges() { + if (this.dataIsPristine) { + return; + } + + this.isLoading = true; + + const newBlockedDomainsSaveState: NeverDomains = {}; + const uniqueBlockedDomains = new Set(this.blockedDomainsState); + + for (const uri of uniqueBlockedDomains) { + if (uri && uri !== "") { + const validatedHost = Utils.getHostname(uri); + + if (!validatedHost) { + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsInvalidDomain", uri), + title: "", + variant: "error", + }); + + // Don't reset via `handleStateUpdate` to allow existing input value correction + this.isLoading = false; + return; + } + + newBlockedDomainsSaveState[validatedHost] = null; + } + } + + try { + const existingState = new Set(this.storedBlockedDomains); + const newState = new Set(Object.keys(newBlockedDomainsSaveState)); + const stateIsUnchanged = + existingState.size === newState.size && + new Set([...existingState, ...newState]).size === existingState.size; + + // The subscriber updates don't trigger if `setNeverDomains` sets an equivalent state + if (stateIsUnchanged) { + // Reset UI state directly + const constructedNeverDomainsState = this.storedBlockedDomains.reduce( + (neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }), + {}, + ); + this.handleStateUpdate(constructedNeverDomainsState); + } else { + await this.domainSettingsService.setBlockedInteractionsUris(newBlockedDomainsSaveState); + } + + this.toastService.showToast({ + message: this.i18nService.t("blockedDomainsSavedSuccess"), + title: "", + variant: "success", + }); + } catch { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + title: "", + variant: "error", + }); + + // Don't reset via `handleStateUpdate` to preserve input values + this.isLoading = false; + } + } + + trackByFunction(index: number, _: string) { + return index; + } +} diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts index 1391ad516fb..7d429bfe4f0 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { QueryList, @@ -17,7 +15,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ButtonModule, @@ -28,6 +25,7 @@ import { LinkModule, SectionComponent, SectionHeaderComponent, + ToastService, TypographyModule, } from "@bitwarden/components"; @@ -62,7 +60,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ], }) export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { - @ViewChildren("uriInput") uriInputElements: QueryList>; + @ViewChildren("uriInput") uriInputElements: QueryList> = + new QueryList(); accountSwitcherEnabled = false; dataIsPristine = true; @@ -77,7 +76,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { constructor( private domainSettingsService: DomainSettingsService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) { this.accountSwitcherEnabled = enableAccountSwitching(); } @@ -156,11 +155,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { const validatedHost = Utils.getHostname(uri); if (!validatedHost) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", uri), - ); + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsInvalidDomain", uri), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to allow existing input value correction this.isLoading = false; @@ -182,7 +181,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { if (stateIsUnchanged) { // Reset UI state directly const constructedNeverDomainsState = this.storedExcludedDomains.reduce( - (neverDomains, uri) => ({ ...neverDomains, [uri]: null }), + (neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }), {}, ); this.handleStateUpdate(constructedNeverDomainsState); @@ -190,13 +189,17 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState); } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("excludedDomainsSavedSuccess"), - ); + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsSavedSuccess"), + title: "", + variant: "success", + }); } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to preserve input values this.isLoading = false; diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 91f926440a0..77d73d7ae65 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -98,7 +98,13 @@ describe("AutofillService", () => { let messageListener: MockProxy; beforeEach(() => { - scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(false)); + scriptInjectorService = new BrowserScriptInjectorService( + domainSettingsService, + platformUtilsService, + logService, + ); inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus); showInlineMenuCardsMock$ = new BehaviorSubject(false); showInlineMenuIdentitiesMock$ = new BehaviorSubject(false); @@ -106,10 +112,10 @@ describe("AutofillService", () => { autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$; autofillSettingsService.showInlineMenuCards$ = showInlineMenuCardsMock$; autofillSettingsService.showInlineMenuIdentities$ = showInlineMenuIdentitiesMock$; + autofillSettingsService.autofillOnPageLoad$ = of(true); activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; - configService = mock(); messageListener = mock(); enableChangedPasswordPromptMock$ = new BehaviorSubject(true); enableAddedLoginPromptMock$ = new BehaviorSubject(true); @@ -132,7 +138,7 @@ describe("AutofillService", () => { userNotificationsSettings, messageListener, ); - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); jest.spyOn(BrowserApi, "tabSendMessage"); }); @@ -385,6 +391,7 @@ describe("AutofillService", () => { ); tabMock = createChromeTabMock(); sender = { tab: tabMock, frameId: 1 }; + jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock); jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); jest .spyOn(autofillService, "getInlineMenuVisibility") diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 555e3a13fa0..34c10508485 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -695,7 +695,6 @@ export default class MainBackground { this.vaultTimeoutSettingsService, ); - this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); this.fileUploadService = new FileUploadService(this.logService, this.apiService); this.cipherFileUploadService = new CipherFileUploadService( this.apiService, @@ -809,6 +808,11 @@ export default class MainBackground { this.authService, ); + this.domainSettingsService = new DefaultDomainSettingsService( + this.stateProvider, + this.configService, + ); + this.themeStateService = new DefaultThemeStateService( this.globalStateProvider, this.configService, @@ -957,6 +961,7 @@ export default class MainBackground { this.totpService = new TotpService(this.cryptoFunctionService, this.logService); this.scriptInjectorService = new BrowserScriptInjectorService( + this.domainSettingsService, this.platformUtilsService, this.logService, ); diff --git a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts index d6ec3dfde96..0919de46776 100644 --- a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts +++ b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts @@ -1,8 +1,22 @@ -import { mock } from "jest-mock-extended"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; +import { + DomainSettingsService, + DefaultDomainSettingsService, +} from "@bitwarden/common/autofill/services/domain-settings.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + FakeStateProvider, + FakeAccountService, + mockAccountServiceWith, +} from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { createChromeTabMock } from "../../autofill/spec/autofill-mocks"; import { BrowserApi } from "../browser/browser-api"; import { @@ -11,8 +25,19 @@ import { } from "./abstractions/script-injector.service"; import { BrowserScriptInjectorService } from "./browser-script-injector.service"; +const mockEquivalentDomains = [ + ["example.com", "exampleapp.com", "example.co.uk", "ejemplo.es"], + ["bitwarden.com", "bitwarden.co.uk", "sm-bitwarden.com"], + ["example.co.uk", "exampleapp.co.uk"], +]; + describe("ScriptInjectorService", () => { const tabId = 1; + const tabMock = createChromeTabMock({ id: tabId }); + const mockBlockedURI = new URL(tabMock.url); + jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); + jest.spyOn(BrowserApi, "isManifestVersion"); + const combinedManifestVersionFile = "content/autofill-init.js"; const mv2SpecificFile = "content/autofill-init-mv2.js"; const mv2Details = { file: mv2SpecificFile }; @@ -22,14 +47,29 @@ describe("ScriptInjectorService", () => { runAt: "document_start", }; const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); + let scriptInjectorService: BrowserScriptInjectorService; - jest.spyOn(BrowserApi, "executeScriptInTab").mockImplementation(); - jest.spyOn(BrowserApi, "isManifestVersion"); - const platformUtilsService = mock(); const logService = mock(); + const platformUtilsService = mock(); + const mockUserId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService); + let configService: MockProxy; + let domainSettingsService: DomainSettingsService; beforeEach(() => { - scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); + jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(false)); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); + domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); + domainSettingsService.blockedInteractionsUris$ = of(null); + scriptInjectorService = new BrowserScriptInjectorService( + domainSettingsService, + platformUtilsService, + logService, + ); + jest.spyOn(scriptInjectorService as any, "buildInjectionDetails"); }); describe("inject", () => { @@ -71,6 +111,58 @@ describe("ScriptInjectorService", () => { { world: "ISOLATED" }, ); }); + + it("skips injecting the script in manifest v3 when the tab domain is a blocked domain", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ [mockBlockedURI.host]: null }); + manifestVersionSpy.mockReturnValue(3); + + await expect(scriptInjectorService["buildInjectionDetails"]).not.toHaveBeenCalled(); + }); + + it("skips injecting the script in manifest v2 when the tab domain is a blocked domain", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ [mockBlockedURI.host]: null }); + manifestVersionSpy.mockReturnValue(2); + + await expect(scriptInjectorService["buildInjectionDetails"]).not.toHaveBeenCalled(); + }); + + it("injects the script in manifest v2 when given combined injection details", async () => { + manifestVersionSpy.mockReturnValue(2); + + await scriptInjectorService.inject({ + tabId, + injectDetails: { + file: combinedManifestVersionFile, + frame: "all_frames", + ...sharedInjectDetails, + }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabId, { + ...sharedInjectDetails, + allFrames: true, + file: combinedManifestVersionFile, + }); + }); + + it("injects the script in manifest v3 when given combined injection details", async () => { + manifestVersionSpy.mockReturnValue(3); + + await scriptInjectorService.inject({ + tabId, + injectDetails: { + file: combinedManifestVersionFile, + frame: 10, + ...sharedInjectDetails, + }, + }); + + expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith( + tabId, + { ...sharedInjectDetails, frameId: 10, file: combinedManifestVersionFile }, + { world: "ISOLATED" }, + ); + }); }); describe("injection of mv2 specific details", () => { diff --git a/apps/browser/src/platform/services/browser-script-injector.service.ts b/apps/browser/src/platform/services/browser-script-injector.service.ts index 2a6b7397a6c..c2bace669dc 100644 --- a/apps/browser/src/platform/services/browser-script-injector.service.ts +++ b/apps/browser/src/platform/services/browser-script-injector.service.ts @@ -1,3 +1,6 @@ +import { firstValueFrom } from "rxjs"; + +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -12,7 +15,10 @@ import { } from "./abstractions/script-injector.service"; export class BrowserScriptInjectorService extends ScriptInjectorService { + blockedDomains: Set = null; + constructor( + private readonly domainSettingsService: DomainSettingsService, private readonly platformUtilsService: PlatformUtilsService, private readonly logService: LogService, ) { @@ -32,6 +38,28 @@ export class BrowserScriptInjectorService extends ScriptInjectorService { throw new Error("No file specified for script injection"); } + const tab = tabId && (await BrowserApi.getTab(tabId)); + const tabURL = tab?.url ? new URL(tab.url) : null; + + // Check if the tab URI is on the disabled URIs list + let injectionAllowedInTab = true; + const blockedDomains = await firstValueFrom( + this.domainSettingsService.blockedInteractionsUris$, + ); + + if (blockedDomains && tabURL?.hostname) { + const blockedDomainsSet = new Set(Object.keys(blockedDomains)); + + injectionAllowedInTab = !(tabURL && blockedDomainsSet.has(tabURL.hostname)); + } + + if (!injectionAllowedInTab) { + this.logService.warning( + `${injectDetails.file} was not injected because ${tabURL?.hostname || "the tab URI"} is on the user's blocked domains list.`, + ); + return; + } + const injectionDetails = this.buildInjectionDetails(injectDetails, file); if (BrowserApi.isManifestVersion(3)) { diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 161c4ca0524..2d53ae7e239 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -75,6 +75,7 @@ import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; +import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-domains.component"; import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; @@ -348,6 +349,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, }, + { + path: "blocked-domains", + component: BlockedDomainsComponent, + canActivate: [authGuard], + data: { elevation: 2 } satisfies RouteDataProperties, + }, ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { path: "excluded-domains", canActivate: [authGuard], diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 5b27833636f..92eb8973235 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -327,7 +327,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DomainSettingsService, useClass: DefaultDomainSettingsService, - deps: [StateProvider], + deps: [StateProvider, ConfigService], }), safeProvider({ provide: AbstractStorageService, @@ -365,7 +365,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: ScriptInjectorService, useClass: BrowserScriptInjectorService, - deps: [PlatformUtilsService, LogService], + deps: [DomainSettingsService, PlatformUtilsService, LogService], }), safeProvider({ provide: VaultTimeoutService, diff --git a/apps/browser/src/tools/background/fileless-importer.background.spec.ts b/apps/browser/src/tools/background/fileless-importer.background.spec.ts index 7b356b18fd5..429a0e12184 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.spec.ts @@ -1,9 +1,10 @@ import { mock } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; +import { of } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -23,18 +24,10 @@ import { FilelessImportPort, FilelessImportType } from "../enums/fileless-import import FilelessImporterBackground from "./fileless-importer.background"; -jest.mock("rxjs", () => { - const rxjs = jest.requireActual("rxjs"); - const { firstValueFrom } = rxjs; - return { - ...rxjs, - firstValueFrom: jest.fn(firstValueFrom), - }; -}); - describe("FilelessImporterBackground ", () => { let filelessImporterBackground: FilelessImporterBackground; const configService = mock(); + const domainSettingsService = mock(); const authService = mock(); const policyService = mock(); const notificationBackground = mock(); @@ -43,9 +36,16 @@ describe("FilelessImporterBackground ", () => { const platformUtilsService = mock(); const logService = mock(); let scriptInjectorService: BrowserScriptInjectorService; + let tabMock: chrome.tabs.Tab; beforeEach(() => { - scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); + domainSettingsService.blockedInteractionsUris$ = of(null); + policyService.policyAppliesToActiveUser$.mockImplementation(() => of(true)); + scriptInjectorService = new BrowserScriptInjectorService( + domainSettingsService, + platformUtilsService, + logService, + ); filelessImporterBackground = new FilelessImporterBackground( configService, authService, @@ -75,12 +75,13 @@ describe("FilelessImporterBackground ", () => { beforeEach(() => { lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter); + tabMock = lpImporterPort.sender.tab; + jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock); manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); executeScriptInTabSpy = jest.spyOn(BrowserApi, "executeScriptInTab").mockResolvedValue(null); jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked); jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true); jest.spyOn(filelessImporterBackground as any, "removeIndividualVault"); - (firstValueFrom as jest.Mock).mockResolvedValue(false); }); it("ignores the port connection if the port name is not present in the set of filelessImportNames", async () => { @@ -105,8 +106,6 @@ describe("FilelessImporterBackground ", () => { }); it("posts a message to the port indicating that the fileless import feature is disabled if the user's policy removes individual vaults", async () => { - (firstValueFrom as jest.Mock).mockResolvedValue(true); - triggerRuntimeOnConnectEvent(lpImporterPort); await flushPromises(); @@ -129,6 +128,8 @@ describe("FilelessImporterBackground ", () => { }); it("posts a message to the port indicating that the fileless import feature is enabled", async () => { + policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); + triggerRuntimeOnConnectEvent(lpImporterPort); await flushPromises(); @@ -139,6 +140,7 @@ describe("FilelessImporterBackground ", () => { }); it("triggers an injection of the `lp-suppress-import-download.js` script in manifest v3", async () => { + policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); manifestVersionSpy.mockReturnValue(3); triggerRuntimeOnConnectEvent(lpImporterPort); @@ -152,6 +154,7 @@ describe("FilelessImporterBackground ", () => { }); it("triggers an injection of the `lp-suppress-import-download-script-append-mv2.js` script in manifest v2", async () => { + policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); manifestVersionSpy.mockReturnValue(2); triggerRuntimeOnConnectEvent(lpImporterPort); @@ -170,9 +173,10 @@ describe("FilelessImporterBackground ", () => { let lpImporterPort: chrome.runtime.Port; beforeEach(async () => { + policyService.policyAppliesToActiveUser$.mockImplementation(() => of(false)); jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked); jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true); - (firstValueFrom as jest.Mock).mockResolvedValue(false); + triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.NotificationBar)); triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.LpImporter)); await flushPromises(); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 2afbae0782f..9f9e45e86d4 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -483,7 +483,29 @@ export class ServiceContainer { this.containerService = new ContainerService(this.keyService, this.encryptService); - this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); + this.configApiService = new ConfigApiService(this.apiService, this.tokenService); + + this.authService = new AuthService( + this.accountService, + this.messagingService, + this.keyService, + this.apiService, + this.stateService, + this.tokenService, + ); + + this.configService = new DefaultConfigService( + this.configApiService, + this.environmentService, + this.logService, + this.stateProvider, + this.authService, + ); + + this.domainSettingsService = new DefaultDomainSettingsService( + this.stateProvider, + this.configService, + ); this.fileUploadService = new FileUploadService(this.logService, this.apiService); @@ -579,25 +601,6 @@ export class ServiceContainer { this.taskSchedulerService = new DefaultTaskSchedulerService(this.logService); - this.authService = new AuthService( - this.accountService, - this.messagingService, - this.keyService, - this.apiService, - this.stateService, - this.tokenService, - ); - - this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - - this.configService = new DefaultConfigService( - this.configApiService, - this.environmentService, - this.logService, - this.stateProvider, - this.authService, - ); - this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.deviceTrustService = new DeviceTrustService( this.keyGenerationService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 149d553696e..583ba82fc98 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -463,6 +463,11 @@ const safeProviders: SafeProvider[] = [ useClass: CipherFileUploadService, deps: [ApiServiceAbstraction, FileUploadServiceAbstraction], }), + safeProvider({ + provide: DomainSettingsService, + useClass: DefaultDomainSettingsService, + deps: [StateProvider, ConfigService], + }), safeProvider({ provide: CipherServiceAbstraction, useFactory: ( @@ -1243,11 +1248,6 @@ const safeProviders: SafeProvider[] = [ useClass: BadgeSettingsService, deps: [StateProvider], }), - safeProvider({ - provide: DomainSettingsService, - useClass: DefaultDomainSettingsService, - deps: [StateProvider], - }), safeProvider({ provide: BiometricStateService, useClass: DefaultBiometricStateService, diff --git a/libs/common/src/autofill/services/domain-settings.service.spec.ts b/libs/common/src/autofill/services/domain-settings.service.spec.ts index 24e3763eb45..a25653f168c 100644 --- a/libs/common/src/autofill/services/domain-settings.service.spec.ts +++ b/libs/common/src/autofill/services/domain-settings.service.spec.ts @@ -1,5 +1,8 @@ +import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; @@ -8,6 +11,7 @@ import { DefaultDomainSettingsService, DomainSettingsService } from "./domain-se describe("DefaultDomainSettingsService", () => { let domainSettingsService: DomainSettingsService; + let configService: MockProxy; const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService); @@ -19,10 +23,13 @@ describe("DefaultDomainSettingsService", () => { ]; beforeEach(() => { - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + configService = mock(); + configService.getFeatureFlag$.mockImplementation(() => of(false)); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); jest.spyOn(domainSettingsService, "getUrlEquivalentDomains"); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); + domainSettingsService.blockedInteractionsUris$ = of(null); }); describe("getUrlEquivalentDomains", () => { diff --git a/libs/common/src/autofill/services/domain-settings.service.ts b/libs/common/src/autofill/services/domain-settings.service.ts index 708341563e0..aeb3af69dae 100644 --- a/libs/common/src/autofill/services/domain-settings.service.ts +++ b/libs/common/src/autofill/services/domain-settings.service.ts @@ -1,6 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { map, Observable } from "rxjs"; +import { map, Observable, switchMap, of } from "rxjs"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { NeverDomains, @@ -8,6 +10,7 @@ import { UriMatchStrategySetting, UriMatchStrategy, } from "../../models/domain/domain-service"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { Utils } from "../../platform/misc/utils"; import { DOMAIN_SETTINGS_DISK, @@ -23,10 +26,20 @@ const SHOW_FAVICONS = new KeyDefinition(DOMAIN_SETTINGS_DISK, "showFavicons", { deserializer: (value: boolean) => value ?? true, }); +// Domain exclusion list for notifications const NEVER_DOMAINS = new KeyDefinition(DOMAIN_SETTINGS_DISK, "neverDomains", { deserializer: (value: NeverDomains) => value ?? null, }); +// Domain exclusion list for content script injections +const BLOCKED_INTERACTIONS_URIS = new KeyDefinition( + DOMAIN_SETTINGS_DISK, + "blockedInteractionsUris", + { + deserializer: (value: NeverDomains) => value ?? null, + }, +); + const EQUIVALENT_DOMAINS = new UserKeyDefinition(DOMAIN_SETTINGS_DISK, "equivalentDomains", { deserializer: (value: EquivalentDomains) => value ?? null, clearOn: ["logout"], @@ -41,15 +54,45 @@ const DEFAULT_URI_MATCH_STRATEGY = new UserKeyDefinition( }, ); +/** + * The Domain Settings service; provides client settings state for "active client view" URI concerns + */ export abstract class DomainSettingsService { + /** + * Indicates if the favicons for ciphers' URIs should be shown instead of a placeholder + */ showFavicons$: Observable; setShowFavicons: (newValue: boolean) => Promise; + + /** + * User-specified URIs for which the client notifications should not appear + */ neverDomains$: Observable; setNeverDomains: (newValue: NeverDomains) => Promise; + + /** + * User-specified URIs for which client content script injections should not occur, and the state + * of banner/notice visibility for those domains within the client + */ + blockedInteractionsUris$: Observable; + setBlockedInteractionsUris: (newValue: NeverDomains) => Promise; + + /** + * URIs which should be treated as equivalent to each other for various concerns (autofill, etc) + */ equivalentDomains$: Observable; setEquivalentDomains: (newValue: EquivalentDomains, userId: UserId) => Promise; + + /** + * User-specified default for URI-matching strategies (for example, when determining relevant + * ciphers for an active browser tab). Can be overridden by cipher-specific settings. + */ defaultUriMatchStrategy$: Observable; setDefaultUriMatchStrategy: (newValue: UriMatchStrategySetting) => Promise; + + /** + * Helper function for the common resolution of a given URL against equivalent domains + */ getUrlEquivalentDomains: (url: string) => Observable>; } @@ -60,19 +103,37 @@ export class DefaultDomainSettingsService implements DomainSettingsService { private neverDomainsState: GlobalState; readonly neverDomains$: Observable; + private blockedInteractionsUrisState: GlobalState; + readonly blockedInteractionsUris$: Observable; + private equivalentDomainsState: ActiveUserState; readonly equivalentDomains$: Observable; private defaultUriMatchStrategyState: ActiveUserState; readonly defaultUriMatchStrategy$: Observable; - constructor(private stateProvider: StateProvider) { + constructor( + private stateProvider: StateProvider, + private configService: ConfigService, + ) { this.showFaviconsState = this.stateProvider.getGlobal(SHOW_FAVICONS); this.showFavicons$ = this.showFaviconsState.state$.pipe(map((x) => x ?? true)); this.neverDomainsState = this.stateProvider.getGlobal(NEVER_DOMAINS); this.neverDomains$ = this.neverDomainsState.state$.pipe(map((x) => x ?? null)); + // Needs to be global to prevent pre-login injections + this.blockedInteractionsUrisState = this.stateProvider.getGlobal(BLOCKED_INTERACTIONS_URIS); + + this.blockedInteractionsUris$ = this.configService + .getFeatureFlag$(FeatureFlag.BlockBrowserInjectionsByDomain) + .pipe( + switchMap((featureIsEnabled) => + featureIsEnabled ? this.blockedInteractionsUrisState.state$ : of({} as NeverDomains), + ), + map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : null)), + ); + this.equivalentDomainsState = this.stateProvider.getActive(EQUIVALENT_DOMAINS); this.equivalentDomains$ = this.equivalentDomainsState.state$.pipe(map((x) => x ?? null)); @@ -90,6 +151,10 @@ export class DefaultDomainSettingsService implements DomainSettingsService { await this.neverDomainsState.update(() => newValue); } + async setBlockedInteractionsUris(newValue: NeverDomains): Promise { + await this.blockedInteractionsUrisState.update(() => newValue); + } + async setEquivalentDomains(newValue: EquivalentDomains, userId: UserId): Promise { await this.stateProvider.getUser(userId, EQUIVALENT_DOMAINS).update(() => newValue); } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 135119bf133..0ab7d47acfc 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -27,6 +27,7 @@ export enum FeatureFlag { SSHKeyVaultItem = "ssh-key-vault-item", SSHAgent = "ssh-agent", NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", + BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api", CipherKeyEncryption = "cipher-key-encryption", VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", @@ -81,6 +82,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.SSHKeyVaultItem]: FALSE, [FeatureFlag.SSHAgent]: FALSE, [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, + [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, [FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, diff --git a/libs/common/src/models/domain/domain-service.ts b/libs/common/src/models/domain/domain-service.ts index 9ff53cc8787..a6b5ecfdaac 100644 --- a/libs/common/src/models/domain/domain-service.ts +++ b/libs/common/src/models/domain/domain-service.ts @@ -21,5 +21,5 @@ export const UriMatchStrategy = { export type UriMatchStrategySetting = (typeof UriMatchStrategy)[keyof typeof UriMatchStrategy]; // using uniqueness properties of object shape over Set for ease of state storability -export type NeverDomains = { [id: string]: null }; +export type NeverDomains = { [id: string]: null | { bannerIsDismissed?: boolean } }; export type EquivalentDomains = string[][]; From 1075d7a79859f7b63d2364de497bddd07c4efd5d Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:56:21 -0500 Subject: [PATCH 075/270] PM-16685 - Web - Fix locking (#12722) --- libs/common/src/services/vault-timeout/vault-timeout.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index f465174bf40..55d5bffa99a 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -138,7 +138,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { await this.collectionService.clearActiveUserCache(); } - await this.folderService.clearDecryptedFolderState(userId); + await this.folderService.clearDecryptedFolderState(lockingUserId); await this.masterPasswordService.clearMasterKey(lockingUserId); await this.stateService.setUserKeyAutoUnlock(null, { userId: lockingUserId }); From 003f5fdae9ce3fb84281f42219153971af4df26b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:38:19 +0100 Subject: [PATCH 076/270] [deps]: Lock file maintenance (#12709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García --- apps/desktop/desktop_native/Cargo.lock | 93 +++++++++++++------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index b40246fca2d..96c7d8955db 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ "event-listener", "event-listener-strategy", @@ -354,9 +354,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "1b1244b10dcd56c92219da4e14caa97e312079e185f04ba3eea25061561dc0a0" dependencies = [ "proc-macro2", "quote", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "shlex", ] @@ -811,9 +811,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.135" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d44ff199ff93242c3afe480ab588d544dd08d72e92885e152ffebc670f076ad" +checksum = "ad7c7515609502d316ab9a24f67dc045132d93bfd3f00713389e90d9898bf30d" dependencies = [ "cc", "cxxbridge-cmd", @@ -825,9 +825,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.135" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fd8f17ad454fc1e4f4ab83abffcc88a532e90350d3ffddcb73030220fcbd52" +checksum = "8bfd16fca6fd420aebbd80d643c201ee4692114a0de208b790b9cd02ceae65fb" dependencies = [ "cc", "codespan-reporting", @@ -839,9 +839,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.135" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717c9c806a9e07fdcb34c84965a414ea40fafe57667187052cf1eb7f5e8a8a9" +checksum = "6c33fd49f5d956a1b7ee5f7a9768d58580c6752838d92e39d0d56439efdedc35" dependencies = [ "clap", "codespan-reporting", @@ -852,15 +852,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.135" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f6515329bf3d98f4073101c7866ff2bec4e635a13acb82e3f3753fff0bf43cb" +checksum = "be0f1077278fac36299cce8446effd19fe93a95eedb10d39265f3bf67b3036c9" [[package]] name = "cxxbridge-macro" -version = "1.0.135" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb93e6a7ce8ec985c02bbb758237a31598b340acbbc3c19c5a4fa6adaaac92ab" +checksum = "3da7e4d6e74af6b79031d264b2f13c3ea70af1978083741c41ffce9308f1f24f" dependencies = [ "proc-macro2", "quote", @@ -1134,7 +1134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1190,9 +1190,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "fs-err" @@ -1354,9 +1354,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "goblin" @@ -1519,7 +1519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1961,9 +1961,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -2300,9 +2300,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -2482,9 +2482,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -2584,18 +2584,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -2604,9 +2604,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -2803,9 +2803,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -2814,9 +2814,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" dependencies = [ "core-foundation-sys", "libc", @@ -2828,15 +2828,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3050,9 +3051,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" @@ -3340,7 +3341,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3636,9 +3637,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] From 91d696307445c81b6c2441de7a75fba9a426cd31 Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:25:26 -0500 Subject: [PATCH 077/270] [PM-14366] Deprecated active user state from billing state service (#12273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated billing state provider to not rely on ActiveUserStateProvider * Updated usages * Resolved browser build * Resolved web build * Resolved CLI build * resolved desktop build * Update apps/cli/src/tools/send/commands/create.command.ts Co-authored-by: ✨ Audrey ✨ * Move subscription visibility logic from component to service * Resolved unit test failures. Using existing userIds where present * Simplified activeUserId access * Resolved typescript strict errors * Resolved broken unit test * Resolved ts strict error --------- Co-authored-by: ✨ Audrey ✨ --- .../browser/main-context-menu-handler.spec.ts | 20 ++- .../browser/main-context-menu-handler.ts | 15 +- .../services/autofill.service.spec.ts | 22 ++- .../src/autofill/services/autofill.service.ts | 3 +- .../browser/src/background/main.background.ts | 3 + .../src/background/runtime.background.ts | 5 +- .../popup/settings/premium-v2.component.ts | 3 + .../popup/send-v2/send-v2.component.spec.ts | 12 +- .../more-from-bitwarden-page-v2.component.ts | 14 +- .../open-attachments.component.spec.ts | 19 ++- .../open-attachments.component.ts | 11 +- apps/cli/src/commands/get.command.ts | 6 +- apps/cli/src/oss-serve-configurator.ts | 2 + .../service-container/service-container.ts | 2 + .../src/tools/send/commands/create.command.ts | 10 +- .../src/tools/send/commands/edit.command.ts | 5 +- apps/cli/src/tools/send/send.program.ts | 2 + apps/cli/src/vault/create.command.ts | 12 +- apps/cli/src/vault/delete.command.ts | 3 +- .../vault/app/accounts/premium.component.ts | 5 +- .../src/vault/app/vault/vault.component.ts | 13 +- .../settings/two-factor-setup.component.ts | 3 + .../emergency-access.component.ts | 10 +- .../two-factor/two-factor-setup.component.ts | 9 +- .../premium/premium-v2.component.ts | 17 ++- .../individual/premium/premium.component.ts | 15 +- .../individual/subscription.component.ts | 8 +- .../individual/user-subscription.component.ts | 7 +- .../src/app/core/guards/has-premium.guard.ts | 13 +- .../src/app/layouts/user-layout.component.ts | 37 ++--- .../reports/pages/reports-home.component.ts | 9 +- .../vault-item-dialog.component.ts | 8 +- .../add-edit-v2.component.spec.ts | 10 +- .../individual-vault/add-edit-v2.component.ts | 16 ++- .../individual-vault/add-edit.component.ts | 11 +- .../services/vault-banners.service.spec.ts | 10 +- .../services/vault-banners.service.ts | 30 +++- .../vault/individual-vault/vault.component.ts | 2 +- .../src/directives/not-premium.directive.ts | 11 +- .../src/directives/premium.directive.ts | 19 ++- .../src/services/jslib-services.module.ts | 2 +- .../src/tools/send/add-edit.component.ts | 19 ++- .../vault/components/attachments.component.ts | 2 +- .../src/vault/components/premium.component.ts | 10 +- .../src/vault/components/view.component.ts | 2 +- .../billing-account-profile-state.service.ts | 21 +-- ...ling-account-profile-state.service.spec.ts | 128 ++++++++++++++---- .../billing-account-profile-state.service.ts | 89 ++++++------ .../new-send-dropdown.component.ts | 10 +- .../send-list-filters.component.spec.ts | 20 ++- .../send-list-filters.component.ts | 14 +- .../attachments-v2-view.component.ts | 15 +- .../login-credentials-view.component.spec.ts | 18 ++- .../login-credentials-view.component.ts | 13 +- .../copy-cipher-field.service.spec.ts | 17 ++- .../src/services/copy-cipher-field.service.ts | 10 +- 56 files changed, 595 insertions(+), 227 deletions(-) diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts index 21eadfaf668..79998b65205 100644 --- a/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts +++ b/apps/browser/src/autofill/browser/main-context-menu-handler.spec.ts @@ -1,12 +1,14 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { NOOP_COMMAND_SUFFIX } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -19,6 +21,7 @@ describe("context-menu", () => { let i18nService: MockProxy; let logService: MockProxy; let billingAccountProfileStateService: MockProxy; + let accountService: MockProxy; let removeAllSpy: jest.SpyInstance void]>; let createSpy: jest.SpyInstance< @@ -34,6 +37,7 @@ describe("context-menu", () => { i18nService = mock(); logService = mock(); billingAccountProfileStateService = mock(); + accountService = mock(); removeAllSpy = jest .spyOn(chrome.contextMenus, "removeAll") @@ -53,8 +57,15 @@ describe("context-menu", () => { i18nService, logService, billingAccountProfileStateService, + accountService, ); autofillSettingsService.enableContextMenu$ = of(true); + accountService.activeAccount$ = of({ + id: "userId" as UserId, + email: "", + emailVerified: false, + name: undefined, + }); }); afterEach(() => jest.resetAllMocks()); @@ -69,7 +80,7 @@ describe("context-menu", () => { }); it("has menu enabled, but does not have premium", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(false)); const createdMenu = await sut.init(); expect(createdMenu).toBeTruthy(); @@ -77,7 +88,7 @@ describe("context-menu", () => { }); it("has menu enabled and has premium", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); const createdMenu = await sut.init(); expect(createdMenu).toBeTruthy(); @@ -131,16 +142,15 @@ describe("context-menu", () => { }); it("create entry for each cipher piece", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); await sut.loadOptions("TEST_TITLE", "1", createCipher()); - // One for autofill, copy username, copy password, and copy totp code expect(createSpy).toHaveBeenCalledTimes(4); }); it("creates a login/unlock item for each context menu action option when user is not authenticated", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); await sut.loadOptions("TEST_TITLE", "NOOP"); diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.ts index e755524da47..41d88439e8f 100644 --- a/apps/browser/src/autofill/browser/main-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/main-context-menu-handler.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AUTOFILL_CARD_ID, AUTOFILL_ID, @@ -149,6 +150,7 @@ export class MainContextMenuHandler { private i18nService: I18nService, private logService: LogService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} /** @@ -168,11 +170,13 @@ export class MainContextMenuHandler { this.initRunning = true; try { + const account = await firstValueFrom(this.accountService.activeAccount$); + const hasPremium = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ); + for (const options of this.initContextMenuItems) { - if ( - options.checkPremiumAccess && - !(await firstValueFrom(this.billingAccountProfileStateService.hasPremiumFromAnySource$)) - ) { + if (options.checkPremiumAccess && !hasPremium) { continue; } @@ -267,8 +271,9 @@ export class MainContextMenuHandler { await createChildItem(COPY_USERNAME_ID); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ); if (canAccessPremium && (!cipher || !Utils.isNullOrEmpty(cipher.login?.totp))) { await createChildItem(COPY_VERIFICATION_CODE_ID); diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 77d73d7ae65..16b11b98866 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -1,4 +1,4 @@ -import { mock, mockReset, MockProxy } from "jest-mock-extended"; +import { mock, MockProxy, mockReset } from "jest-mock-extended"; import { BehaviorSubject, of, Subject } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -730,7 +730,9 @@ describe("AutofillService", () => { it("throws an error if an autofill did not occur for any of the passed pages", async () => { autofillOptions.tab.url = "https://a-different-url.com"; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(true)); try { await autofillService.doAutoFill(autofillOptions); @@ -912,7 +914,9 @@ describe("AutofillService", () => { it("returns a TOTP value", async () => { const totpCode = "123456"; autofillOptions.cipher.login.totp = "totp"; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(true)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true); jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode); @@ -925,7 +929,9 @@ describe("AutofillService", () => { it("does not return a TOTP value if the user does not have premium features", async () => { autofillOptions.cipher.login.totp = "totp"; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(false)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true); const autofillResult = await autofillService.doAutoFill(autofillOptions); @@ -959,7 +965,9 @@ describe("AutofillService", () => { it("returns a null value if the user cannot access premium and the organization does not use TOTP", async () => { autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.organizationUseTotp = false; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(false)); const autofillResult = await autofillService.doAutoFill(autofillOptions); @@ -969,7 +977,9 @@ describe("AutofillService", () => { it("returns a null value if the user has disabled `auto TOTP copy`", async () => { autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.organizationUseTotp = true; - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + jest + .spyOn(billingAccountProfileStateService, "hasPremiumFromAnySource$") + .mockImplementation(() => of(true)); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(false); jest.spyOn(totpService, "getCode"); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 093f4bfb638..6d0e9954ade 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -416,8 +416,9 @@ export default class AutofillService implements AutofillServiceInterface { let totp: string | null = null; + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeAccount.id), ); const defaultUriMatch = await this.getDefaultUriMatchStrategy(); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 34c10508485..ff240ec8cac 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -792,6 +792,8 @@ export default class MainBackground { this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService( this.stateProvider, + this.platformUtilsService, + this.apiService, ); this.ssoLoginService = new SsoLoginService(this.stateProvider); @@ -1229,6 +1231,7 @@ export default class MainBackground { this.i18nService, this.logService, this.billingAccountProfileStateService, + this.accountService, ); this.cipherContextMenuHandler = new CipherContextMenuHandler( diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 56ad7909e61..c31ec94be90 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -202,8 +202,11 @@ export default class RuntimeBackground { return await this.configService.getFeatureFlag(FeatureFlag.InlineMenuFieldQualification); } case "getUserPremiumStatus": { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const result = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); return result; } diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.ts b/apps/browser/src/billing/popup/settings/premium-v2.component.ts index c17adcd52fe..f658f71a209 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -7,6 +7,7 @@ import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -56,6 +57,7 @@ export class PremiumV2Component extends BasePremiumComponent { dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( i18nService, @@ -66,6 +68,7 @@ export class PremiumV2Component extends BasePremiumComponent { dialogService, environmentService, billingAccountProfileStateService, + accountService, ); // Support old price string. Can be removed in future once all translations are properly updated. diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts index 506d7146dd6..c3f4634a6c2 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts @@ -91,7 +91,17 @@ describe("SendV2Component", () => { CurrentAccountComponent, ], providers: [ - { provide: AccountService, useValue: mock() }, + { + provide: AccountService, + useValue: { + activeAccount$: of({ + id: "123", + email: "test@email.com", + emailVerified: true, + name: "Test User", + }), + }, + }, { provide: AuthService, useValue: mock() }, { provide: AvatarService, useValue: mock() }, { diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts index 2d451dddaa7..8b880e88671 100644 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts @@ -1,10 +1,11 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { Observable, firstValueFrom } from "rxjs"; +import { Observable, firstValueFrom, of, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { DialogService, ItemModule } from "@bitwarden/components"; @@ -36,12 +37,19 @@ export class MoreFromBitwardenPageV2Component { constructor( private dialogService: DialogService, - billingAccountProfileStateService: BillingAccountProfileStateService, + private billingAccountProfileStateService: BillingAccountProfileStateService, private environmentService: EnvironmentService, private organizationService: OrganizationService, private familiesPolicyService: FamiliesPolicyService, + private accountService: AccountService, ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + ), + ); this.familySponsorshipAvailable$ = this.organizationService.familySponsorshipAvailable$; this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$(); this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index 8c1e0641b03..4f6c4aa07cf 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -10,7 +10,6 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -55,7 +54,14 @@ describe("OpenAttachmentsComponent", () => { const showFilePopoutMessage = jest.fn().mockReturnValue(false); const mockUserId = Utils.newGuid() as UserId; - const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + const accountService = { + activeAccount$: of({ + id: mockUserId, + email: "test@email.com", + emailVerified: true, + name: "Test User", + }), + }; beforeEach(async () => { openCurrentPagePopout.mockClear(); @@ -63,6 +69,7 @@ describe("OpenAttachmentsComponent", () => { showToast.mockClear(); getOrganization.mockClear(); showFilePopoutMessage.mockClear(); + hasPremiumFromAnySource$.next(true); await TestBed.configureTestingModule({ imports: [OpenAttachmentsComponent, RouterTestingModule], @@ -96,7 +103,7 @@ describe("OpenAttachmentsComponent", () => { }).compileComponents(); }); - beforeEach(() => { + beforeEach(async () => { fixture = TestBed.createComponent(OpenAttachmentsComponent); component = fixture.componentInstance; component.cipherId = "5555-444-3333" as CipherId; @@ -107,7 +114,7 @@ describe("OpenAttachmentsComponent", () => { it("opens attachments in new popout", async () => { showFilePopoutMessage.mockReturnValue(true); - + component.canAccessAttachments = true; await component.ngOnInit(); await component.openAttachments(); @@ -120,7 +127,7 @@ describe("OpenAttachmentsComponent", () => { it("opens attachments in same window", async () => { showFilePopoutMessage.mockReturnValue(false); - + component.canAccessAttachments = true; await component.ngOnInit(); await component.openAttachments(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index ca620531ca8..5e27ccd5c41 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -4,7 +4,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom, map, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -54,8 +54,13 @@ export class OpenAttachmentsComponent implements OnInit { private filePopoutUtilsService: FilePopoutUtilsService, private accountService: AccountService, ) { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntilDestroyed()) + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntilDestroyed(), + ) .subscribe((canAccessPremium) => { this.canAccessAttachments = canAccessPremium; }); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 7c3cc7caa9f..454db2858ab 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -262,8 +262,9 @@ export class GetCommand extends DownloadCommand { return Response.error("Couldn't generate TOTP code."); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (!canAccessPremium) { const originalCipher = await this.cipherService.get(cipher.id); @@ -347,8 +348,9 @@ export class GetCommand extends DownloadCommand { return Response.multipleResults(attachments.map((a) => a.id)); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (!canAccessPremium) { const originalCipher = await this.cipherService.get(cipher.id); diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 9bd3a2bee5f..be476d19814 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -149,6 +149,7 @@ export class OssServeConfigurator { this.serviceContainer.environmentService, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); this.sendDeleteCommand = new SendDeleteCommand( this.serviceContainer.sendService, @@ -166,6 +167,7 @@ export class OssServeConfigurator { this.sendGetCommand, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); this.sendListCommand = new SendListCommand( this.serviceContainer.sendService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 9f9e45e86d4..bef4d52fad5 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -597,6 +597,8 @@ export class ServiceContainer { this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService( this.stateProvider, + this.platformUtilsService, + this.apiService, ); this.taskSchedulerService = new DefaultTaskSchedulerService(this.logService); diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts index 09c7937be3a..eff351be22a 100644 --- a/apps/cli/src/tools/send/commands/create.command.ts +++ b/apps/cli/src/tools/send/commands/create.command.ts @@ -3,8 +3,9 @@ import * as fs from "fs"; import * as path from "path"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, switchMap } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; @@ -23,6 +24,7 @@ export class SendCreateCommand { private environmentService: EnvironmentService, private sendApiService: SendApiService, private accountProfileService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async run(requestJson: any, cmdOptions: Record) { @@ -78,6 +80,10 @@ export class SendCreateCommand { req.key = null; req.maxAccessCount = maxAccessCount; + const hasPremium$ = this.accountService.activeAccount$.pipe( + switchMap(({ id }) => this.accountProfileService.hasPremiumFromAnySource$(id)), + ); + switch (req.type) { case SendType.File: if (process.env.BW_SERVE === "true") { @@ -86,7 +92,7 @@ export class SendCreateCommand { ); } - if (!(await firstValueFrom(this.accountProfileService.hasPremiumFromAnySource$))) { + if (!(await firstValueFrom(hasPremium$))) { return Response.error("Premium status is required to use this feature."); } diff --git a/apps/cli/src/tools/send/commands/edit.command.ts b/apps/cli/src/tools/send/commands/edit.command.ts index 2793c450fb6..11508d5c417 100644 --- a/apps/cli/src/tools/send/commands/edit.command.ts +++ b/apps/cli/src/tools/send/commands/edit.command.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; @@ -19,6 +20,7 @@ export class SendEditCommand { private getCommand: SendGetCommand, private sendApiService: SendApiService, private accountProfileService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async run(requestJson: string, cmdOptions: Record): Promise { @@ -61,8 +63,9 @@ export class SendEditCommand { return Response.badRequest("Cannot change a Send's type"); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (send.type === SendType.File && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index b59ae770380..052faa33867 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -258,6 +258,7 @@ export class SendProgram extends BaseProgram { getCmd, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); const response = await cmd.run(encodedJson, options); this.processResponse(response); @@ -331,6 +332,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.environmentService, this.serviceContainer.sendApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.accountService, ); return await cmd.run(encodedJson, options); } diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 47e91cb55ff..13cd666754f 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -136,10 +136,13 @@ export class CreateCommand { return Response.notFound(); } - if ( - cipher.organizationId == null && - !(await firstValueFrom(this.accountProfileService.hasPremiumFromAnySource$)) - ) { + const activeUserId = await firstValueFrom(this.activeUserId$); + + const canAccessPremium = await firstValueFrom( + this.accountProfileService.hasPremiumFromAnySource$(activeUserId), + ); + + if (cipher.organizationId == null && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); } @@ -152,7 +155,6 @@ export class CreateCommand { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); const updatedCipher = await this.cipherService.saveAttachmentRawWithServer( cipher, fileName, diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 6b66b8bc7bb..a285f8f5b34 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -89,8 +89,9 @@ export class DeleteCommand { return Response.error("Attachment `" + id + "` was not found."); } + const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$, + this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (cipher.organizationId == null && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); diff --git a/apps/desktop/src/vault/app/accounts/premium.component.ts b/apps/desktop/src/vault/app/accounts/premium.component.ts index 373e5d88177..4b547384545 100644 --- a/apps/desktop/src/vault/app/accounts/premium.component.ts +++ b/apps/desktop/src/vault/app/accounts/premium.component.ts @@ -2,13 +2,13 @@ import { Component } from "@angular/core"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { 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 } from "@bitwarden/components"; @Component({ @@ -22,10 +22,10 @@ export class PremiumComponent extends BasePremiumComponent { apiService: ApiService, configService: ConfigService, logService: LogService, - stateService: StateService, dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( i18nService, @@ -36,6 +36,7 @@ export class PremiumComponent extends BasePremiumComponent { dialogService, environmentService, billingAccountProfileStateService, + accountService, ); } } diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index f375b303024..ec2dbec5b8f 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -10,7 +10,7 @@ import { ViewContainerRef, } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, takeUntil, switchMap } from "rxjs"; import { first } from "rxjs/operators"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -18,6 +18,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -111,11 +112,17 @@ export class VaultComponent implements OnInit, OnDestroy { private dialogService: DialogService, private billingAccountProfileStateService: BillingAccountProfileStateService, private configService: ConfigService, + private accountService: AccountService, ) {} async ngOnInit() { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.componentIsDestroyed$)) + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntil(this.componentIsDestroyed$), + ) .subscribe((canAccessPremium: boolean) => { this.userHasPremiumAccess = canAccessPremium; }); diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 48a844caa22..9cc6341c082 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -10,6 +10,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -37,6 +38,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme private route: ActivatedRoute, private organizationService: OrganizationService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { super( dialogService, @@ -45,6 +47,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme messagingService, policyService, billingAccountProfileStateService, + accountService, ); } diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index 316be3ed65c..5271e50c9a3 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -1,11 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { lastValueFrom, Observable, firstValueFrom } from "rxjs"; +import { lastValueFrom, Observable, firstValueFrom, switchMap } from "rxjs"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -69,8 +70,13 @@ export class EmergencyAccessComponent implements OnInit { billingAccountProfileStateService: BillingAccountProfileStateService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, private toastService: ToastService, + private accountService: AccountService, ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); } async ngOnInit() { diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts index 14cf63d3f4e..3b20718873d 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -10,6 +10,7 @@ import { Subject, Subscription, takeUntil, + switchMap, } from "rxjs"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -18,6 +19,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; @@ -69,8 +71,13 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { protected messagingService: MessagingService, protected policyService: PolicyService, billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); } async ngOnInit() { diff --git a/apps/web/src/app/billing/individual/premium/premium-v2.component.ts b/apps/web/src/app/billing/individual/premium/premium-v2.component.ts index 2abab57b7e0..11b55f92b40 100644 --- a/apps/web/src/app/billing/individual/premium/premium-v2.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium-v2.component.ts @@ -4,10 +4,11 @@ import { Component, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, concatMap, from, Observable, of } from "rxjs"; +import { combineLatest, concatMap, from, Observable, of, switchMap } from "rxjs"; import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; @@ -65,14 +66,22 @@ export class PremiumV2Component { private toastService: ToastService, private tokenService: TokenService, private taxService: TaxServiceAbstraction, + private accountService: AccountService, ) { this.isSelfHost = this.platformUtilsService.isSelfHost(); - this.hasPremiumFromAnyOrganization$ = - this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$; + this.hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id), + ), + ); combineLatest([ - this.billingAccountProfileStateService.hasPremiumPersonally$, + this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumPersonally$(account.id), + ), + ), this.environmentService.cloudWebVaultUrl$, ]) .pipe( diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/premium.component.ts index 76ca25c8cc6..f96f573cd4d 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium.component.ts @@ -4,10 +4,11 @@ import { Component, OnInit, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { firstValueFrom, Observable } from "rxjs"; +import { firstValueFrom, Observable, switchMap } from "rxjs"; import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; @@ -58,9 +59,14 @@ export class PremiumComponent implements OnInit { private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, private taxService: TaxServiceAbstraction, + private accountService: AccountService, ) { this.selfHosted = platformUtilsService.isSelfHost(); - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.canAccessPremium$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); this.addonForm.controls.additionalStorage.valueChanges .pipe(debounceTime(1000), takeUntilDestroyed()) @@ -75,7 +81,10 @@ export class PremiumComponent implements OnInit { } async ngOnInit() { this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$); - if (await firstValueFrom(this.billingAccountProfileStateService.hasPremiumPersonally$)) { + const account = await firstValueFrom(this.accountService.activeAccount$); + if ( + await firstValueFrom(this.billingAccountProfileStateService.hasPremiumPersonally$(account.id)) + ) { // 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(["/settings/subscription/user-subscription"]); diff --git a/apps/web/src/app/billing/individual/subscription.component.ts b/apps/web/src/app/billing/individual/subscription.component.ts index d8d435d8fe5..edd16ca81fe 100644 --- a/apps/web/src/app/billing/individual/subscription.component.ts +++ b/apps/web/src/app/billing/individual/subscription.component.ts @@ -1,8 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { Observable } from "rxjs"; +import { Observable, switchMap } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -16,8 +17,11 @@ export class SubscriptionComponent implements OnInit { constructor( private platformUtilsService: PlatformUtilsService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { - this.hasPremium$ = billingAccountProfileStateService.hasPremiumPersonally$; + this.hasPremium$ = accountService.activeAccount$.pipe( + switchMap((account) => billingAccountProfileStateService.hasPremiumPersonally$(account.id)), + ); } ngOnInit() { diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 57d5ef314ec..97b4725e6d7 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -5,6 +5,7 @@ import { Router } from "@angular/router"; import { firstValueFrom, lastValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -60,6 +61,7 @@ export class UserSubscriptionComponent implements OnInit { private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, private configService: ConfigService, + private accountService: AccountService, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -75,7 +77,10 @@ export class UserSubscriptionComponent implements OnInit { return; } - if (await firstValueFrom(this.billingAccountProfileStateService.hasPremiumPersonally$)) { + const userId = await firstValueFrom(this.accountService.activeAccount$); + if ( + await firstValueFrom(this.billingAccountProfileStateService.hasPremiumPersonally$(userId.id)) + ) { this.loading = true; this.sub = await this.apiService.getUserSubscription(); } else { diff --git a/apps/web/src/app/core/guards/has-premium.guard.ts b/apps/web/src/app/core/guards/has-premium.guard.ts index ab544dafb61..61853b25cb8 100644 --- a/apps/web/src/app/core/guards/has-premium.guard.ts +++ b/apps/web/src/app/core/guards/has-premium.guard.ts @@ -6,9 +6,10 @@ import { CanActivateFn, UrlTree, } from "@angular/router"; -import { Observable } from "rxjs"; -import { tap } from "rxjs/operators"; +import { Observable, of } from "rxjs"; +import { switchMap, tap } from "rxjs/operators"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -24,8 +25,14 @@ export function hasPremiumGuard(): CanActivateFn { const router = inject(Router); const messagingService = inject(MessagingService); const billingAccountProfileStateService = inject(BillingAccountProfileStateService); + const accountService = inject(AccountService); - return billingAccountProfileStateService.hasPremiumFromAnySource$.pipe( + return accountService.activeAccount$.pipe( + switchMap((account) => + account + ? billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + ), tap((userHasPremium: boolean) => { if (!userHasPremium) { messagingService.send("premiumRequired"); diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index 18277abebef..f0ac3ef9b48 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -3,12 +3,11 @@ import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { Observable, concatMap, combineLatest } from "rxjs"; +import { Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { IconModule } from "@bitwarden/components"; @@ -38,35 +37,19 @@ export class UserLayoutComponent implements OnInit { protected showSubscription$: Observable; constructor( - private platformUtilsService: PlatformUtilsService, - private apiService: ApiService, private syncService: SyncService, private billingAccountProfileStateService: BillingAccountProfileStateService, - ) {} + private accountService: AccountService, + ) { + this.showSubscription$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.canViewSubscription$(account.id), + ), + ); + } async ngOnInit() { document.body.classList.remove("layout_frontend"); - await this.syncService.fullSync(false); - - // We want to hide the subscription menu for organizations that provide premium. - // Except if the user has premium personally or has a billing history. - this.showSubscription$ = combineLatest([ - this.billingAccountProfileStateService.hasPremiumPersonally$, - this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$, - ]).pipe( - concatMap(async ([hasPremiumPersonally, hasPremiumFromOrg]) => { - const isCloud = !this.platformUtilsService.isSelfHost(); - - let billing = null; - if (isCloud) { - // TODO: We should remove the need to call this! - billing = await this.apiService.getUserBillingHistory(); - } - - const cloudAndBillingHistory = isCloud && !billing?.hasNoHistory; - return hasPremiumPersonally || !hasPremiumFromOrg || cloudAndBillingHistory; - }), - ); } } diff --git a/apps/web/src/app/tools/reports/pages/reports-home.component.ts b/apps/web/src/app/tools/reports/pages/reports-home.component.ts index 961c24bb017..604d66f6858 100644 --- a/apps/web/src/app/tools/reports/pages/reports-home.component.ts +++ b/apps/web/src/app/tools/reports/pages/reports-home.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { reports, ReportType } from "../reports"; @@ -15,11 +16,15 @@ import { ReportEntry, ReportVariant } from "../shared"; export class ReportsHomeComponent implements OnInit { reports: ReportEntry[]; - constructor(private billingAccountProfileStateService: BillingAccountProfileStateService) {} + constructor( + private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, + ) {} async ngOnInit(): Promise { + const account = await firstValueFrom(this.accountService.activeAccount$); const userHasPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ); const reportRequiresPremium = userHasPremium ? ReportVariant.Enabled diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index a9ff49c5791..c91314f68d7 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -4,7 +4,7 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, Observable, Subject } from "rxjs"; +import { firstValueFrom, Observable, Subject, switchMap } from "rxjs"; import { map } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; @@ -183,7 +183,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { * Flag to indicate if the user has access to attachments via a premium subscription. * @protected */ - protected canAccessAttachments$ = this.billingAccountProfileStateService.hasPremiumFromAnySource$; + protected canAccessAttachments$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); protected get loadingForm() { return this.loadForm && !this.formReady; diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts index 6c126235234..1ca9b0de47c 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts @@ -9,6 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -34,6 +35,7 @@ describe("AddEditComponentV2", () => { let messagingService: MockProxy; let folderService: MockProxy; let collectionService: MockProxy; + let accountService: MockProxy; const mockParams = { cloneMode: false, @@ -55,7 +57,9 @@ describe("AddEditComponentV2", () => { ); billingAccountProfileStateService = mock(); - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockImplementation((userId) => + of(true), + ); activatedRoute = mock(); activatedRoute.queryParams = of({}); @@ -68,6 +72,9 @@ describe("AddEditComponentV2", () => { collectionService = mock(); collectionService.decryptedCollections$ = of([]); + accountService = mock(); + accountService.activeAccount$ = of({ id: "test-id" } as any); + const mockDefaultCipherFormConfigService = { buildConfig: jest.fn().mockResolvedValue({ allowPersonal: true, @@ -97,6 +104,7 @@ describe("AddEditComponentV2", () => { provide: PasswordGenerationServiceAbstraction, useValue: mock(), }, + { provide: AccountService, useValue: accountService }, ], }).compileComponents(); diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts index 5237db15b3c..c0a17a4aeb8 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts @@ -4,8 +4,10 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, Inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { switchMap } from "rxjs"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; @@ -85,10 +87,16 @@ export class AddEditComponentV2 implements OnInit { private i18nService: I18nService, private dialogService: DialogService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntilDestroyed()) - .subscribe((canAccessPremium) => { + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntilDestroyed(), + ) + .subscribe((canAccessPremium: boolean) => { this.canAccessAttachments = canAccessPremium; }); } diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 7038ffb898a..53a9e839064 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; @@ -116,9 +116,14 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On this.hasPasswordHistory = this.cipher.hasPasswordHistory; this.cleanUp(); - this.canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a.id)), ); + + this.canAccessPremium = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), + ); + if (this.showTotp()) { await this.totpUpdateCode(); const interval = this.totpService.getTimeInterval(this.cipher.login.totp); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts index 9a5537985b8..7f7e0f075b7 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts @@ -1,6 +1,7 @@ import { TestBed } from "@angular/core/testing"; import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; @@ -21,7 +22,8 @@ describe("VaultBannersService", () => { let service: VaultBannersService; const isSelfHost = jest.fn().mockReturnValue(false); const hasPremiumFromAnySource$ = new BehaviorSubject(false); - const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith("user-id" as UserId)); + const userId = "user-id" as UserId; + const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); const getEmailVerified = jest.fn().mockResolvedValue(true); const hasMasterPassword = jest.fn().mockResolvedValue(true); const getKdfConfig = jest @@ -44,15 +46,15 @@ describe("VaultBannersService", () => { }, { provide: BillingAccountProfileStateService, - useValue: { hasPremiumFromAnySource$: hasPremiumFromAnySource$ }, + useValue: { hasPremiumFromAnySource$: () => hasPremiumFromAnySource$ }, }, { provide: StateProvider, useValue: fakeStateProvider, }, { - provide: PlatformUtilsService, - useValue: { isSelfHost }, + provide: AccountService, + useValue: mockAccountServiceWith(userId), }, { provide: TokenService, diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index 6ab37ea0cdd..c18b046e35e 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -1,7 +1,17 @@ import { Injectable } from "@angular/core"; -import { Subject, Observable, combineLatest, firstValueFrom, map } from "rxjs"; -import { mergeMap, take } from "rxjs/operators"; +import { + Subject, + Observable, + combineLatest, + firstValueFrom, + map, + mergeMap, + take, + switchMap, + of, +} from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; @@ -74,15 +84,23 @@ export class VaultBannersService { private platformUtilsService: PlatformUtilsService, private kdfConfigService: KdfConfigService, private syncService: SyncService, + private accountService: AccountService, ) { this.pollUntilSynced(); this.premiumBannerState = this.stateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY); this.sessionBannerState = this.stateProvider.getActive(BANNERS_DISMISSED_DISK_KEY); - const premiumSources$ = combineLatest([ - this.billingAccountProfileStateService.hasPremiumFromAnySource$, - this.premiumBannerState.state$, - ]); + const premiumSources$ = this.accountService.activeAccount$.pipe( + take(1), + switchMap((account) => { + return combineLatest([ + account + ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + this.premiumBannerState.state$, + ]); + }), + ); this.shouldShowPremiumBanner$ = this.syncCompleted$.pipe( take(1), // Wait until the first sync is complete before considering the premium status diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 18a1d8b338a..fe030162d19 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -467,7 +467,7 @@ export class VaultComponent implements OnInit, OnDestroy { switchMap(() => combineLatest([ filter$, - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(this.activeUserId), allCollections$, this.organizationService.organizations$, ciphers$, diff --git a/libs/angular/src/directives/not-premium.directive.ts b/libs/angular/src/directives/not-premium.directive.ts index 3aee9b192d2..5a1c636c009 100644 --- a/libs/angular/src/directives/not-premium.directive.ts +++ b/libs/angular/src/directives/not-premium.directive.ts @@ -1,6 +1,7 @@ import { Directive, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; /** @@ -14,11 +15,19 @@ export class NotPremiumDirective implements OnInit { private templateRef: TemplateRef, private viewContainer: ViewContainerRef, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async ngOnInit(): Promise { + const account = await firstValueFrom(this.accountService.activeAccount$); + + if (!account) { + this.viewContainer.createEmbeddedView(this.templateRef); + return; + } + const premium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ); if (premium) { diff --git a/libs/angular/src/directives/premium.directive.ts b/libs/angular/src/directives/premium.directive.ts index d475669a1ab..2188205ba65 100644 --- a/libs/angular/src/directives/premium.directive.ts +++ b/libs/angular/src/directives/premium.directive.ts @@ -1,6 +1,7 @@ import { Directive, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; -import { Subject, takeUntil } from "rxjs"; +import { of, Subject, switchMap, takeUntil } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; /** @@ -16,16 +17,24 @@ export class PremiumDirective implements OnInit, OnDestroy { private templateRef: TemplateRef, private viewContainer: ViewContainerRef, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async ngOnInit(): Promise { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.directiveIsDestroyed$)) + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + ), + takeUntil(this.directiveIsDestroyed$), + ) .subscribe((premium: boolean) => { if (premium) { - this.viewContainer.clear(); - } else { this.viewContainer.createEmbeddedView(this.templateRef); + } else { + this.viewContainer.clear(); } }); } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 583ba82fc98..f9a72f24476 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1281,7 +1281,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: BillingAccountProfileStateService, useClass: DefaultBillingAccountProfileStateService, - deps: [StateProvider], + deps: [StateProvider, PlatformUtilsServiceAbstraction, ApiServiceAbstraction], }), safeProvider({ provide: OrganizationManagementPreferencesService, diff --git a/libs/angular/src/tools/send/add-edit.component.ts b/libs/angular/src/tools/send/add-edit.component.ts index 228abec98a9..aeee1fa104c 100644 --- a/libs/angular/src/tools/send/add-edit.component.ts +++ b/libs/angular/src/tools/send/add-edit.component.ts @@ -3,7 +3,15 @@ import { DatePipe } from "@angular/common"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Subject, firstValueFrom, takeUntil, map, BehaviorSubject, concatMap } from "rxjs"; +import { + Subject, + firstValueFrom, + takeUntil, + map, + BehaviorSubject, + concatMap, + switchMap, +} from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -197,8 +205,13 @@ export class AddEditComponent implements OnInit, OnDestroy { const env = await firstValueFrom(this.environmentService.environment$); this.sendLinkBaseUrl = env.getSendUrl(); - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntil(this.destroy$)) + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntil(this.destroy$), + ) .subscribe((hasPremiumFromAnySource) => { this.canAccessPremium = hasPremiumFromAnySource; }); diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 521d38a1f47..425b4be2840 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -209,7 +209,7 @@ export class AttachmentsComponent implements OnInit { ); const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null; diff --git a/libs/angular/src/vault/components/premium.component.ts b/libs/angular/src/vault/components/premium.component.ts index 2ad25f2e45a..8b1f215ef42 100644 --- a/libs/angular/src/vault/components/premium.component.ts +++ b/libs/angular/src/vault/components/premium.component.ts @@ -1,9 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { OnInit, Directive } from "@angular/core"; -import { firstValueFrom, Observable } from "rxjs"; +import { firstValueFrom, Observable, switchMap } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -30,8 +31,13 @@ export class PremiumComponent implements OnInit { protected dialogService: DialogService, private environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { - this.isPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.isPremium$ = accountService.activeAccount$.pipe( + switchMap((account) => + billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); } async ngOnInit() { diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 6bea4cd6150..fc12aeff2f2 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -148,7 +148,7 @@ export class ViewComponent implements OnDestroy, OnInit { await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); this.canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); this.showPremiumRequiredTotp = this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp; diff --git a/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts b/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts index 8fbbc7c1c91..a4253226880 100644 --- a/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts +++ b/libs/common/src/billing/abstractions/account/billing-account-profile-state.service.ts @@ -11,27 +11,32 @@ export type BillingAccountProfile = { export abstract class BillingAccountProfileStateService { /** - * Emits `true` when the active user's account has been granted premium from any of the + * Emits `true` when the user's account has been granted premium from any of the * organizations it is a member of. Otherwise, emits `false` */ - hasPremiumFromAnyOrganization$: Observable; + abstract hasPremiumFromAnyOrganization$(userId: UserId): Observable; /** - * Emits `true` when the active user's account has an active premium subscription at the + * Emits `true` when the user's account has an active premium subscription at the * individual user level */ - hasPremiumPersonally$: Observable; + abstract hasPremiumPersonally$(userId: UserId): Observable; /** * Emits `true` when either `hasPremiumPersonally` or `hasPremiumFromAnyOrganization` is `true` */ - hasPremiumFromAnySource$: Observable; + abstract hasPremiumFromAnySource$(userId: UserId): Observable; /** - * Sets the active user's premium status fields upon every full sync, either from their personal + * Emits `true` when the subscription menu item should be shown in navigation. + * This is hidden for organizations that provide premium, except if the user has premium personally + * or has a billing history. + */ + abstract canViewSubscription$(userId: UserId): Observable; + + /** + * Sets the user's premium status fields upon every full sync, either from their personal * subscription to premium, or an organization they're a part of that grants them premium. - * @param hasPremiumPersonally - * @param hasPremiumFromAnyOrganization */ abstract setHasPremium( hasPremiumPersonally: boolean, diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts index 7e0dee0eedf..372d8099865 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts @@ -1,5 +1,9 @@ import { firstValueFrom } from "rxjs"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + import { FakeAccountService, mockAccountServiceWith, @@ -19,14 +23,26 @@ describe("BillingAccountProfileStateService", () => { let sut: DefaultBillingAccountProfileStateService; let userBillingAccountProfileState: FakeSingleUserState; let accountService: FakeAccountService; + let platformUtilsService: jest.Mocked; + let apiService: jest.Mocked; const userId = "fakeUserId" as UserId; beforeEach(() => { accountService = mockAccountServiceWith(userId); stateProvider = new FakeStateProvider(accountService); + platformUtilsService = { + isSelfHost: jest.fn(), + } as any; + apiService = { + getUserBillingHistory: jest.fn(), + } as any; - sut = new DefaultBillingAccountProfileStateService(stateProvider); + sut = new DefaultBillingAccountProfileStateService( + stateProvider, + platformUtilsService, + apiService, + ); userBillingAccountProfileState = stateProvider.singleUser.getFake( userId, @@ -45,7 +61,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: true, }); - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$(userId))).toBe(true); }); it("return false when they do not have premium from an organization", async () => { @@ -54,13 +70,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(false); - }); - - it("returns false when there is no active user", async () => { - await accountService.switchAccount(null); - - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(false); + expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$(userId))).toBe(false); }); }); @@ -71,7 +81,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumPersonally$(userId))).toBe(true); }); it("returns false when the user does not have premium personally", async () => { @@ -80,13 +90,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(false); - }); - - it("returns false when there is no active user", async () => { - await accountService.switchAccount(null); - - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(false); + expect(await firstValueFrom(sut.hasPremiumPersonally$(userId))).toBe(false); }); }); @@ -97,7 +101,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: false, }); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); }); it("returns true when the user has premium from an organization", async () => { @@ -106,7 +110,7 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: true, }); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); }); it("returns true when they have premium personally AND from an organization", async () => { @@ -115,23 +119,87 @@ describe("BillingAccountProfileStateService", () => { hasPremiumFromAnyOrganization: true, }); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); - }); - - it("returns false when there is no active user", async () => { - await accountService.switchAccount(null); - - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(false); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); }); }); describe("setHasPremium", () => { - it("should update the active users state when called", async () => { + it("should update the user's state when called", async () => { await sut.setHasPremium(true, false, userId); - expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$)).toBe(false); - expect(await firstValueFrom(sut.hasPremiumPersonally$)).toBe(true); - expect(await firstValueFrom(sut.hasPremiumFromAnySource$)).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnyOrganization$(userId))).toBe(false); + expect(await firstValueFrom(sut.hasPremiumPersonally$(userId))).toBe(true); + expect(await firstValueFrom(sut.hasPremiumFromAnySource$(userId))).toBe(true); + }); + }); + + describe("canViewSubscription$", () => { + beforeEach(() => { + platformUtilsService.isSelfHost.mockReturnValue(false); + apiService.getUserBillingHistory.mockResolvedValue( + new BillingHistoryResponse({ invoices: [], transactions: [] }), + ); + }); + + it("returns true when user has premium personally", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: true, + hasPremiumFromAnyOrganization: true, + }); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(true); + }); + + it("returns true when user has no premium from any source", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: false, + }); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(true); + }); + + it("returns true when user has billing history in cloud environment", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: true, + }); + platformUtilsService.isSelfHost.mockReturnValue(false); + apiService.getUserBillingHistory.mockResolvedValue( + new BillingHistoryResponse({ + invoices: [{ id: "1" }], + transactions: [{ id: "2" }], + }), + ); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(true); + }); + + it("returns false when user has no premium personally, has org premium, and no billing history", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: true, + }); + platformUtilsService.isSelfHost.mockReturnValue(false); + apiService.getUserBillingHistory.mockResolvedValue( + new BillingHistoryResponse({ + invoices: [], + transactions: [], + }), + ); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(false); + }); + + it("returns false when user has no premium personally, has org premium, in self-hosted environment", async () => { + userBillingAccountProfileState.nextState({ + hasPremiumPersonally: false, + hasPremiumFromAnyOrganization: true, + }); + platformUtilsService.isSelfHost.mockReturnValue(true); + + expect(await firstValueFrom(sut.canViewSubscription$(userId))).toBe(false); + expect(apiService.getUserBillingHistory).not.toHaveBeenCalled(); }); }); }); diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts index 7d256da9714..579a81eeb5c 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts @@ -1,11 +1,9 @@ -import { map, Observable, of, switchMap } from "rxjs"; +import { map, Observable, combineLatest, concatMap } from "rxjs"; -import { - ActiveUserState, - BILLING_DISK, - StateProvider, - UserKeyDefinition, -} from "../../../platform/state"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { BILLING_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { BillingAccountProfile, @@ -22,42 +20,34 @@ export const BILLING_ACCOUNT_PROFILE_KEY_DEFINITION = new UserKeyDefinition; + constructor( + private readonly stateProvider: StateProvider, + private readonly platformUtilsService: PlatformUtilsService, + private readonly apiService: ApiService, + ) {} - hasPremiumFromAnyOrganization$: Observable; - hasPremiumPersonally$: Observable; - hasPremiumFromAnySource$: Observable; + hasPremiumFromAnyOrganization$(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION) + .state$.pipe(map((profile) => !!profile?.hasPremiumFromAnyOrganization)); + } - constructor(private readonly stateProvider: StateProvider) { - this.billingAccountProfileState = stateProvider.getActive( - BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, - ); + hasPremiumPersonally$(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION) + .state$.pipe(map((profile) => !!profile?.hasPremiumPersonally)); + } - // Setup an observable that will always track the currently active user - // but will fallback to emitting null when there is no active user. - const billingAccountProfileOrNull = stateProvider.activeUserId$.pipe( - switchMap((userId) => - userId != null - ? stateProvider.getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION).state$ - : of(null), - ), - ); - - this.hasPremiumFromAnyOrganization$ = billingAccountProfileOrNull.pipe( - map((billingAccountProfile) => !!billingAccountProfile?.hasPremiumFromAnyOrganization), - ); - - this.hasPremiumPersonally$ = billingAccountProfileOrNull.pipe( - map((billingAccountProfile) => !!billingAccountProfile?.hasPremiumPersonally), - ); - - this.hasPremiumFromAnySource$ = billingAccountProfileOrNull.pipe( - map( - (billingAccountProfile) => - billingAccountProfile?.hasPremiumFromAnyOrganization === true || - billingAccountProfile?.hasPremiumPersonally === true, - ), - ); + hasPremiumFromAnySource$(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, BILLING_ACCOUNT_PROFILE_KEY_DEFINITION) + .state$.pipe( + map( + (profile) => + profile?.hasPremiumFromAnyOrganization === true || + profile?.hasPremiumPersonally === true, + ), + ); } async setHasPremium( @@ -72,4 +62,23 @@ export class DefaultBillingAccountProfileStateService implements BillingAccountP }; }); } + + canViewSubscription$(userId: UserId): Observable { + return combineLatest([ + this.hasPremiumPersonally$(userId), + this.hasPremiumFromAnyOrganization$(userId), + ]).pipe( + concatMap(async ([hasPremiumPersonally, hasPremiumFromOrg]) => { + const isCloud = !this.platformUtilsService.isSelfHost(); + + let billing = null; + if (isCloud) { + billing = await this.apiService.getUserBillingHistory(); + } + + const cloudAndBillingHistory = isCloud && !billing?.hasNoHistory; + return hasPremiumPersonally || !hasPremiumFromOrg || cloudAndBillingHistory; + }), + ); + } } diff --git a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts index d446bcb92ab..19f9d3a174a 100644 --- a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts +++ b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts @@ -4,6 +4,7 @@ import { Router, RouterLink } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { BadgeModule, ButtonModule, MenuModule } from "@bitwarden/components"; @@ -24,11 +25,18 @@ export class NewSendDropdownComponent implements OnInit { constructor( private router: Router, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} async ngOnInit() { + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + this.hasNoPremium = true; + return; + } + this.hasNoPremium = !(await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$, + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), )); } diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts index bb687c6d5e9..2f6bf691c1d 100644 --- a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts +++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.spec.ts @@ -5,8 +5,10 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { ChipSelectComponent } from "@bitwarden/components"; import { SendListFiltersService } from "../services/send-list-filters.service"; @@ -18,13 +20,22 @@ describe("SendListFiltersComponent", () => { let fixture: ComponentFixture; let sendListFiltersService: SendListFiltersService; let billingAccountProfileStateService: MockProxy; + let accountService: MockProxy; + const userId = "userId" as UserId; beforeEach(async () => { sendListFiltersService = new SendListFiltersService(mock(), new FormBuilder()); sendListFiltersService.resetFilterForm = jest.fn(); billingAccountProfileStateService = mock(); + accountService = mock(); - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + accountService.activeAccount$ = of({ + id: userId, + email: "test@email.com", + emailVerified: true, + name: "Test User", + }); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); await TestBed.configureTestingModule({ imports: [ @@ -37,10 +48,8 @@ describe("SendListFiltersComponent", () => { providers: [ { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: SendListFiltersService, useValue: sendListFiltersService }, - { - provide: BillingAccountProfileStateService, - useValue: billingAccountProfileStateService, - }, + { provide: BillingAccountProfileStateService, useValue: billingAccountProfileStateService }, + { provide: AccountService, useValue: accountService }, ], }).compileComponents(); @@ -57,6 +66,7 @@ describe("SendListFiltersComponent", () => { let canAccessPremium: boolean | undefined; component["canAccessPremium$"].subscribe((value) => (canAccessPremium = value)); expect(canAccessPremium).toBe(true); + expect(billingAccountProfileStateService.hasPremiumFromAnySource$).toHaveBeenCalledWith(userId); }); it("should call resetFilterForm on ngOnDestroy", () => { diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts index b313ced742a..d42eab382e9 100644 --- a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts +++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts @@ -1,10 +1,11 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy } from "@angular/core"; import { ReactiveFormsModule } from "@angular/forms"; -import { Observable } from "rxjs"; +import { Observable, of, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ChipSelectComponent } from "@bitwarden/components"; import { SendListFiltersService } from "../services/send-list-filters.service"; @@ -23,8 +24,15 @@ export class SendListFiltersComponent implements OnDestroy { constructor( private sendListFiltersService: SendListFiltersService, billingAccountProfileStateService: BillingAccountProfileStateService, + accountService: AccountService, ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; + this.canAccessPremium$ = accountService.activeAccount$.pipe( + switchMap((account) => + account + ? billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) + : of(false), + ), + ); } ngOnDestroy(): void { diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts index acfdfa3337d..0c2ca35cbbc 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts @@ -6,6 +6,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NEVER, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { StateProvider } from "@bitwarden/common/platform/state"; import { OrganizationId } from "@bitwarden/common/types/guid"; @@ -47,16 +48,22 @@ export class AttachmentsV2ViewComponent { private keyService: KeyService, private billingAccountProfileStateService: BillingAccountProfileStateService, private stateProvider: StateProvider, + private accountService: AccountService, ) { this.subscribeToHasPremiumCheck(); this.subscribeToOrgKey(); } subscribeToHasPremiumCheck() { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntilDestroyed()) - .subscribe((data) => { - this.canAccessPremium = data; + this.accountService.activeAccount$ + .pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntilDestroyed(), + ) + .subscribe((hasPremium) => { + this.canAccessPremium = hasPremium; }); } diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts index c8ac0598c97..9c09028650e 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts @@ -6,10 +6,12 @@ import { BehaviorSubject } from "rxjs"; import { CopyClickDirective } from "@bitwarden/angular/directives/copy-click.directive"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -26,6 +28,17 @@ describe("LoginCredentialsViewComponent", () => { let fixture: ComponentFixture; const hasPremiumFromAnySource$ = new BehaviorSubject(true); + const mockAccount = { + id: "test-user-id" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + type: 0, + status: 0, + kdf: 0, + kdfIterations: 0, + }; + const activeAccount$ = new BehaviorSubject(mockAccount); const cipher = { id: "cipher-id", @@ -48,8 +61,11 @@ describe("LoginCredentialsViewComponent", () => { providers: [ { provide: BillingAccountProfileStateService, - useValue: mock({ hasPremiumFromAnySource$ }), + useValue: mock({ + hasPremiumFromAnySource$: () => hasPremiumFromAnySource$, + }), }, + { provide: AccountService, useValue: mock({ activeAccount$ }) }, { provide: PremiumUpgradePromptService, useValue: mock() }, { provide: EventCollectionService, useValue: mock({ collect }) }, { provide: PlatformUtilsService, useValue: mock() }, diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 0c42c2ddda7..b24fcdfa1fd 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -2,10 +2,11 @@ // @ts-strict-ignore import { CommonModule, DatePipe } from "@angular/common"; import { Component, inject, Input } from "@angular/core"; -import { Observable, shareReplay } from "rxjs"; +import { Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -50,10 +51,11 @@ type TotpCodeValues = { export class LoginCredentialsViewComponent { @Input() cipher: CipherView; - isPremium$: Observable = - this.billingAccountProfileStateService.hasPremiumFromAnySource$.pipe( - shareReplay({ refCount: true, bufferSize: 1 }), - ); + isPremium$: Observable = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); showPasswordCount: boolean = false; passwordRevealed: boolean = false; totpCodeCopyObj: TotpCodeValues; @@ -64,6 +66,7 @@ export class LoginCredentialsViewComponent { private i18nService: I18nService, private premiumUpgradeService: PremiumUpgradePromptService, private eventCollectionService: EventCollectionService, + private accountService: AccountService, ) {} get fido2CredentialCreationDateValue(): string { diff --git a/libs/vault/src/services/copy-cipher-field.service.spec.ts b/libs/vault/src/services/copy-cipher-field.service.spec.ts index 48510b2efd9..5a273c0828f 100644 --- a/libs/vault/src/services/copy-cipher-field.service.spec.ts +++ b/libs/vault/src/services/copy-cipher-field.service.spec.ts @@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -22,6 +23,8 @@ describe("CopyCipherFieldService", () => { let totpService: MockProxy; let i18nService: MockProxy; let billingAccountProfileStateService: MockProxy; + let accountService: MockProxy; + const userId = "userId"; beforeEach(() => { platformUtilsService = mock(); @@ -31,6 +34,9 @@ describe("CopyCipherFieldService", () => { totpService = mock(); i18nService = mock(); billingAccountProfileStateService = mock(); + accountService = mock(); + + accountService.activeAccount$ = of({ id: userId } as Account); service = new CopyCipherFieldService( platformUtilsService, @@ -40,6 +46,7 @@ describe("CopyCipherFieldService", () => { totpService, i18nService, billingAccountProfileStateService, + accountService, ); }); @@ -128,12 +135,15 @@ describe("CopyCipherFieldService", () => { }); it("should get TOTP code when allowed from premium", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true)); totpService.getCode.mockResolvedValue("123456"); const result = await service.copy(valueToCopy, actionType, cipher, skipReprompt); expect(result).toBeTruthy(); expect(totpService.getCode).toHaveBeenCalledWith(valueToCopy); expect(platformUtilsService.copyToClipboard).toHaveBeenCalledWith("123456"); + expect(billingAccountProfileStateService.hasPremiumFromAnySource$).toHaveBeenCalledWith( + userId, + ); }); it("should get TOTP code when allowed from organization", async () => { @@ -146,11 +156,14 @@ describe("CopyCipherFieldService", () => { }); it("should return early when the user is not allowed to use TOTP", async () => { - billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false); + billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(false)); const result = await service.copy(valueToCopy, actionType, cipher, skipReprompt); expect(result).toBeFalsy(); expect(totpService.getCode).not.toHaveBeenCalled(); expect(platformUtilsService.copyToClipboard).not.toHaveBeenCalled(); + expect(billingAccountProfileStateService.hasPremiumFromAnySource$).toHaveBeenCalledWith( + userId, + ); }); it("should return early when TOTP is not set", async () => { diff --git a/libs/vault/src/services/copy-cipher-field.service.ts b/libs/vault/src/services/copy-cipher-field.service.ts index bfcf3495865..2805f3e7541 100644 --- a/libs/vault/src/services/copy-cipher-field.service.ts +++ b/libs/vault/src/services/copy-cipher-field.service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -87,6 +88,7 @@ export class CopyCipherFieldService { private totpService: TotpService, private i18nService: I18nService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private accountService: AccountService, ) {} /** @@ -148,10 +150,16 @@ export class CopyCipherFieldService { * Determines if TOTP generation is allowed for a cipher and user. */ async totpAllowed(cipher: CipherView): Promise { + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (!activeAccount?.id) { + return false; + } return ( (cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || - (await firstValueFrom(this.billingAccountProfileStateService.hasPremiumFromAnySource$))) + (await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeAccount.id), + ))) ); } } From c0d3fe15d10300353e435a1d524c287dc05b60d4 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:47:51 -0600 Subject: [PATCH 078/270] [PM-11528] Move Lock to KM ownership (#12407) * update code owners * Move lock component v2 to KM * Add @bitwarden/key-management/angular to tsconfigs * Move lock component service to KM * Move lock component v1 to KM * Update imports * Move into @bitwarden/key-management * Revert "Move into @bitwarden/key-management" This reverts commit b7514fb8c2ba3ee57331829b297949aa9052bf4d. * Add to tsconfig.libs --- .github/CODEOWNERS | 1 + .../extension-lock-component.service.spec.ts | 4 ++-- .../services/extension-lock-component.service.ts | 14 +++++++------- apps/browser/src/popup/app-routing.module.ts | 2 +- apps/browser/src/popup/services/services.module.ts | 4 ++-- apps/browser/tsconfig.json | 1 + apps/cli/tsconfig.json | 1 + apps/desktop/src/app/app-routing.module.ts | 2 +- apps/desktop/src/app/services/services.module.ts | 4 ++-- .../desktop-lock-component.service.spec.ts | 2 +- .../services/desktop-lock-component.service.ts | 10 +++++----- apps/desktop/tsconfig.json | 1 + apps/web/src/app/auth/core/services/index.ts | 1 - apps/web/src/app/core/core.module.ts | 4 ++-- .../services/web-lock-component.service.spec.ts | 0 .../lock}/services/web-lock-component.service.ts | 2 +- apps/web/src/app/oss-routing.module.ts | 2 +- apps/web/tsconfig.json | 1 + bitwarden_license/bit-cli/tsconfig.json | 1 + bitwarden_license/bit-common/tsconfig.json | 1 + bitwarden_license/bit-web/tsconfig.json | 1 + libs/auth/src/angular/index.ts | 4 ---- libs/key-management/src/angular/index.ts | 10 ++++++++++ .../angular/lock/components}/lock.component.html | 0 .../src/angular/lock/components}/lock.component.ts | 7 +++---- .../lock/services}/lock-component.service.ts | 0 libs/shared/tsconfig.libs.json | 1 + tsconfig.json | 1 + 28 files changed, 48 insertions(+), 34 deletions(-) rename apps/browser/src/{ => key-management/lock}/services/extension-lock-component.service.spec.ts (98%) rename apps/browser/src/{ => key-management/lock}/services/extension-lock-component.service.ts (94%) rename apps/desktop/src/{ => key-management/lock}/services/desktop-lock-component.service.spec.ts (99%) rename apps/desktop/src/{ => key-management/lock}/services/desktop-lock-component.service.ts (99%) rename apps/web/src/app/{auth/core => key-management/lock}/services/web-lock-component.service.spec.ts (100%) rename apps/web/src/app/{auth/core => key-management/lock}/services/web-lock-component.service.ts (98%) create mode 100644 libs/key-management/src/angular/index.ts rename libs/{auth/src/angular/lock => key-management/src/angular/lock/components}/lock.component.html (100%) rename libs/{auth/src/angular/lock => key-management/src/angular/lock/components}/lock.component.ts (99%) rename libs/{auth/src/angular/lock => key-management/src/angular/lock/services}/lock-component.service.ts (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e38277877bb..cb36d87b9e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -126,6 +126,7 @@ apps/web/src/app/key-management @bitwarden/team-key-management-dev apps/browser/src/key-management @bitwarden/team-key-management-dev apps/cli/src/key-management @bitwarden/team-key-management-dev libs/key-management @bitwarden/team-key-management-dev +libs/common/src/key-management @bitwarden/team-key-management-dev apps/desktop/destkop_native/core/src/biometric/ @bitwarden/team-key-management-dev apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-key-management-dev diff --git a/apps/browser/src/services/extension-lock-component.service.spec.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts similarity index 98% rename from apps/browser/src/services/extension-lock-component.service.spec.ts rename to apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts index a8a019662ef..272201c6ede 100644 --- a/apps/browser/src/services/extension-lock-component.service.spec.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts @@ -2,7 +2,6 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/auth/angular"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, @@ -11,8 +10,9 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService, BiometricsService } from "@bitwarden/key-management"; +import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/key-management/angular"; -import { BrowserRouterService } from "../platform/popup/services/browser-router.service"; +import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; import { ExtensionLockComponentService } from "./extension-lock-component.service"; diff --git a/apps/browser/src/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts similarity index 94% rename from apps/browser/src/services/extension-lock-component.service.ts rename to apps/browser/src/key-management/lock/services/extension-lock-component.service.ts index 4fc2ef69b21..07fb2ec6b87 100644 --- a/apps/browser/src/services/extension-lock-component.service.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -3,11 +3,6 @@ import { inject } from "@angular/core"; import { combineLatest, defer, map, Observable } from "rxjs"; -import { - BiometricsDisableReason, - LockComponentService, - UnlockOptions, -} from "@bitwarden/auth/angular"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, @@ -17,9 +12,14 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService, BiometricsService } from "@bitwarden/key-management"; +import { + LockComponentService, + BiometricsDisableReason, + UnlockOptions, +} from "@bitwarden/key-management/angular"; -import { BiometricErrors, BiometricErrorTypes } from "../models/biometricErrors"; -import { BrowserRouterService } from "../platform/popup/services/browser-router.service"; +import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; +import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; export class ExtensionLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 2d53ae7e239..7cc5bbe2f82 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -25,7 +25,6 @@ import { LoginComponent, LoginSecondaryContentComponent, LockIcon, - LockComponent, LoginViaAuthRequestComponent, PasswordHintComponent, RegistrationFinishComponent, @@ -43,6 +42,7 @@ import { TwoFactorTimeoutIcon, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { LockComponent } from "@bitwarden/key-management/angular"; import { NewDeviceVerificationNoticePageOneComponent, NewDeviceVerificationNoticePageTwoComponent, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 92eb8973235..6542eb9c814 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -24,7 +24,6 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services. import { AnonLayoutWrapperDataService, LoginComponentService, - LockComponentService, SsoComponentService, LoginDecryptionOptionsService, } from "@bitwarden/auth/angular"; @@ -115,6 +114,7 @@ import { BiometricStateService, BiometricsService, } from "@bitwarden/key-management"; +import { LockComponentService } from "@bitwarden/key-management/angular"; import { PasswordRepromptService } from "@bitwarden/vault"; import { ForegroundLockService } from "../../auth/popup/accounts/foreground-lock.service"; @@ -127,6 +127,7 @@ import AutofillService from "../../autofill/services/autofill.service"; import { InlineMenuFieldQualificationService } from "../../autofill/services/inline-menu-field-qualification.service"; import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics"; import { BrowserKeyService } from "../../key-management/browser-key.service"; +import { ExtensionLockComponentService } from "../../key-management/lock/services/extension-lock-component.service"; import { BrowserApi } from "../../platform/browser/browser-api"; import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator"; /* eslint-disable no-restricted-imports */ @@ -150,7 +151,6 @@ import { BrowserStorageServiceProvider } from "../../platform/storage/browser-st import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; import { ForegroundSyncService } from "../../platform/sync/foreground-sync.service"; import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; -import { ExtensionLockComponentService } from "../../services/extension-lock-component.service"; import { ForegroundVaultTimeoutService } from "../../services/vault-timeout/foreground-vault-timeout.service"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service"; diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index 6b53186e076..c1ef1443acc 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -34,6 +34,7 @@ "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/vault": ["../../libs/vault/src"] }, "plugins": [ diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 3853cd93126..0668ecacdb4 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -26,6 +26,7 @@ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/node/*": ["../../libs/node/src/*"] }, "plugins": [ diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index 7e82bb004fa..cd4932c616a 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -22,7 +22,6 @@ import { LoginComponent, LoginSecondaryContentComponent, LockIcon, - LockComponent, LoginViaAuthRequestComponent, PasswordHintComponent, RegistrationFinishComponent, @@ -40,6 +39,7 @@ import { TwoFactorTimeoutIcon, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { LockComponent } from "@bitwarden/key-management/angular"; import { NewDeviceVerificationNoticePageOneComponent, NewDeviceVerificationNoticePageTwoComponent, diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 0f541907995..87c2a833073 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -24,7 +24,6 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services. import { LoginComponentService, SetPasswordJitService, - LockComponentService, SsoComponentService, DefaultSsoComponentService, } from "@bitwarden/auth/angular"; @@ -96,6 +95,7 @@ import { BiometricStateService, BiometricsService, } from "@bitwarden/key-management"; +import { LockComponentService } from "@bitwarden/key-management/angular"; import { DesktopLoginApprovalComponentService } from "../../auth/login/desktop-login-approval-component.service"; import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service"; @@ -103,6 +103,7 @@ import { DesktopAutofillSettingsService } from "../../autofill/services/desktop- import { DesktopAutofillService } from "../../autofill/services/desktop-autofill.service"; import { DesktopFido2UserInterfaceService } from "../../autofill/services/desktop-fido2-user-interface.service"; import { ElectronBiometricsService } from "../../key-management/biometrics/electron-biometrics.service"; +import { DesktopLockComponentService } from "../../key-management/lock/services/desktop-lock-component.service"; import { flagEnabled } from "../../platform/flags"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { ElectronKeyService } from "../../platform/services/electron-key.service"; @@ -118,7 +119,6 @@ import { I18nRendererService } from "../../platform/services/i18n.renderer.servi import { fromIpcMessaging } from "../../platform/utils/from-ipc-messaging"; import { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme"; import { BiometricMessageHandlerService } from "../../services/biometric-message-handler.service"; -import { DesktopLockComponentService } from "../../services/desktop-lock-component.service"; import { DuckDuckGoMessageHandlerService } from "../../services/duckduckgo-message-handler.service"; import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service"; import { NativeMessagingService } from "../../services/native-messaging.service"; diff --git a/apps/desktop/src/services/desktop-lock-component.service.spec.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts similarity index 99% rename from apps/desktop/src/services/desktop-lock-component.service.spec.ts rename to apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts index 0d673a5b51c..2d60cdeb663 100644 --- a/apps/desktop/src/services/desktop-lock-component.service.spec.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts @@ -2,7 +2,6 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/auth/angular"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, @@ -12,6 +11,7 @@ import { DeviceType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService, BiometricsService } from "@bitwarden/key-management"; +import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/key-management/angular"; import { DesktopLockComponentService } from "./desktop-lock-component.service"; diff --git a/apps/desktop/src/services/desktop-lock-component.service.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts similarity index 99% rename from apps/desktop/src/services/desktop-lock-component.service.ts rename to apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts index 7402779121f..76232fd3196 100644 --- a/apps/desktop/src/services/desktop-lock-component.service.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts @@ -1,11 +1,6 @@ import { inject } from "@angular/core"; import { combineLatest, defer, map, Observable } from "rxjs"; -import { - BiometricsDisableReason, - LockComponentService, - UnlockOptions, -} from "@bitwarden/auth/angular"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, @@ -16,6 +11,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { UserId } from "@bitwarden/common/types/guid"; import { KeyService, BiometricsService } from "@bitwarden/key-management"; +import { + BiometricsDisableReason, + LockComponentService, + UnlockOptions, +} from "@bitwarden/key-management/angular"; export class DesktopLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 2d9de5fee49..da61ef22dd4 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -29,6 +29,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/node/*": ["../../libs/node/src/*"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts index c14292d7c6d..6275ad4f4f3 100644 --- a/apps/web/src/app/auth/core/services/index.ts +++ b/apps/web/src/app/auth/core/services/index.ts @@ -3,4 +3,3 @@ export * from "./login-decryption-options"; export * from "./webauthn-login"; export * from "./set-password-jit"; export * from "./registration"; -export * from "./web-lock-component.service"; diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 2dd1db9fdb6..8f21dfa2c8b 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -30,7 +30,6 @@ import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/serv import { RegistrationFinishService as RegistrationFinishServiceAbstraction, LoginComponentService, - LockComponentService, SetPasswordJitService, SsoComponentService, LoginDecryptionOptionsService, @@ -92,6 +91,7 @@ import { KeyService as KeyServiceAbstraction, BiometricsService, } from "@bitwarden/key-management"; +import { LockComponentService } from "@bitwarden/key-management/angular"; import { flagEnabled } from "../../utils/flags"; import { PolicyListService } from "../admin-console/core/policy-list.service"; @@ -99,13 +99,13 @@ import { WebSetPasswordJitService, WebRegistrationFinishService, WebLoginComponentService, - WebLockComponentService, WebLoginDecryptionOptionsService, } from "../auth"; import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service"; import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service"; import { HtmlStorageService } from "../core/html-storage.service"; import { I18nService } from "../core/i18n.service"; +import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service"; import { WebProcessReloadService } from "../key-management/services/web-process-reload.service"; import { WebBiometricsService } from "../key-management/web-biometric.service"; import { WebEnvironmentService } from "../platform/web-environment.service"; diff --git a/apps/web/src/app/auth/core/services/web-lock-component.service.spec.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts similarity index 100% rename from apps/web/src/app/auth/core/services/web-lock-component.service.spec.ts rename to apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts diff --git a/apps/web/src/app/auth/core/services/web-lock-component.service.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts similarity index 98% rename from apps/web/src/app/auth/core/services/web-lock-component.service.ts rename to apps/web/src/app/key-management/lock/services/web-lock-component.service.ts index e24f299e23b..dc124983c9a 100644 --- a/apps/web/src/app/auth/core/services/web-lock-component.service.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts @@ -1,12 +1,12 @@ import { inject } from "@angular/core"; import { map, Observable } from "rxjs"; -import { LockComponentService, UnlockOptions } from "@bitwarden/auth/angular"; import { UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; export class WebLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index ad536110b74..fadcc28f832 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -25,7 +25,6 @@ import { RegistrationLinkExpiredComponent, LoginComponent, LoginSecondaryContentComponent, - LockComponent, LockIcon, TwoFactorTimeoutIcon, UserLockIcon, @@ -40,6 +39,7 @@ import { LoginDecryptionOptionsComponent, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { LockComponent } from "@bitwarden/key-management/angular"; import { NewDeviceVerificationNoticePageOneComponent, NewDeviceVerificationNoticePageTwoComponent, diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 3799945ea98..678db7c4af5 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -25,6 +25,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], diff --git a/bitwarden_license/bit-cli/tsconfig.json b/bitwarden_license/bit-cli/tsconfig.json index e3d6cc5c7b7..92a206f44db 100644 --- a/bitwarden_license/bit-cli/tsconfig.json +++ b/bitwarden_license/bit-cli/tsconfig.json @@ -24,6 +24,7 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 03f3bd2d2f1..a0a44f2ab30 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -23,6 +23,7 @@ "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["../../apps/web/src/*"], diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 09de92d355d..c4304ec2bd9 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -24,6 +24,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index 817687ef2bc..66111f3e5af 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -57,10 +57,6 @@ export * from "./user-verification/user-verification-dialog.component"; export * from "./user-verification/user-verification-dialog.types"; export * from "./user-verification/user-verification-form-input.component"; -// lock -export * from "./lock/lock.component"; -export * from "./lock/lock-component.service"; - // vault timeout export * from "./vault-timeout-input/vault-timeout-input.component"; diff --git a/libs/key-management/src/angular/index.ts b/libs/key-management/src/angular/index.ts new file mode 100644 index 00000000000..d7fadc52ce6 --- /dev/null +++ b/libs/key-management/src/angular/index.ts @@ -0,0 +1,10 @@ +/** + * This barrel file should only contain Angular exports + */ + +export { LockComponent } from "./lock/components/lock.component"; +export { + LockComponentService, + BiometricsDisableReason, + UnlockOptions, +} from "./lock/services/lock-component.service"; diff --git a/libs/auth/src/angular/lock/lock.component.html b/libs/key-management/src/angular/lock/components/lock.component.html similarity index 100% rename from libs/auth/src/angular/lock/lock.component.html rename to libs/key-management/src/angular/lock/components/lock.component.html diff --git a/libs/auth/src/angular/lock/lock.component.ts b/libs/key-management/src/angular/lock/components/lock.component.ts similarity index 99% rename from libs/auth/src/angular/lock/lock.component.ts rename to libs/key-management/src/angular/lock/components/lock.component.ts index aa7b43c2e53..fda870bb2ed 100644 --- a/libs/auth/src/angular/lock/lock.component.ts +++ b/libs/key-management/src/angular/lock/components/lock.component.ts @@ -7,6 +7,8 @@ import { Router } from "@angular/router"; import { BehaviorSubject, firstValueFrom, Subject, switchMap, take, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular"; +import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -43,15 +45,12 @@ import { UserAsymmetricKeysRegenerationService, } from "@bitwarden/key-management"; -import { PinServiceAbstraction } from "../../common/abstractions"; -import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; - import { UnlockOption, LockComponentService, UnlockOptions, UnlockOptionValue, -} from "./lock-component.service"; +} from "../services/lock-component.service"; const BroadcasterSubscriptionId = "LockComponent"; diff --git a/libs/auth/src/angular/lock/lock-component.service.ts b/libs/key-management/src/angular/lock/services/lock-component.service.ts similarity index 100% rename from libs/auth/src/angular/lock/lock-component.service.ts rename to libs/key-management/src/angular/lock/services/lock-component.service.ts diff --git a/libs/shared/tsconfig.libs.json b/libs/shared/tsconfig.libs.json index 6057152419c..2366507918e 100644 --- a/libs/shared/tsconfig.libs.json +++ b/libs/shared/tsconfig.libs.json @@ -20,6 +20,7 @@ "@bitwarden/importer/core": ["../importer/src"], "@bitwarden/importer/ui": ["../importer/src/components"], "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/key-management/angular": ["../key-management/src/angular"], "@bitwarden/platform": ["../platform/src"], "@bitwarden/send-ui": ["../tools/send/send-ui/src"], "@bitwarden/tools-card": ["../tools/card/src"], diff --git a/tsconfig.json b/tsconfig.json index 47b561e0829..91b4ee7dd6b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,7 @@ "@bitwarden/importer/core": ["./libs/importer/src"], "@bitwarden/importer/ui": ["./libs/importer/src/components"], "@bitwarden/key-management": ["./libs/key-management/src"], + "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["./libs/tools/card/src"], From 9ca3d0653d74ef21b6694c750ba88bddebc61b52 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 7 Jan 2025 17:28:35 +0100 Subject: [PATCH 079/270] Fix strict typescript for Component Library stories (#12423) Fixing some low hanging fruits for moving CL to strict typescript. This primarily removes the types from args since TS infers them differently. We previously needed them since storybook would use any for args but now provides proper typings --- libs/components/src/button/button.stories.ts | 6 ++---- .../components/src/dialog/dialog/dialog.stories.ts | 8 +++----- .../simple-configurable-dialog.service.stories.ts | 6 ++---- .../src/form-field/bit-validators.stories.ts | 6 ++---- .../src/form-field/form-field.stories.ts | 14 ++++++-------- libs/components/src/search/search.stories.ts | 4 +--- libs/components/src/toast/toast.stories.ts | 6 ++---- 7 files changed, 18 insertions(+), 32 deletions(-) diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index ed3dfc4e134..3654442801c 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Meta, StoryObj } from "@storybook/angular"; import { ButtonComponent } from "./button.component"; @@ -107,13 +105,13 @@ export const DisabledWithAttribute: Story = { }; export const Block: Story = { - render: (args: ButtonComponent) => ({ + render: (args) => ({ props: args, template: ` [block]="true" Link - + block Link diff --git a/libs/components/src/dialog/dialog/dialog.stories.ts b/libs/components/src/dialog/dialog/dialog.stories.ts index 1525d2e0171..7cb6f40aa5b 100644 --- a/libs/components/src/dialog/dialog/dialog.stories.ts +++ b/libs/components/src/dialog/dialog/dialog.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; @@ -78,7 +76,7 @@ export default { type Story = StoryObj; export const Default: Story = { - render: (args: DialogComponent) => ({ + render: (args) => ({ props: args, template: ` @@ -142,7 +140,7 @@ export const Loading: Story = { }; export const ScrollingContent: Story = { - render: (args: DialogComponent) => ({ + render: (args) => ({ props: args, template: ` @@ -197,7 +195,7 @@ export const TabContent: Story = { }; export const WithCards: Story = { - render: (args: DialogComponent) => ({ + render: (args) => ({ props: { formObj: new FormGroup({ name: new FormControl(""), diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts index 409d691beb0..4f21b8611b3 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component } from "@angular/core"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; @@ -72,7 +70,7 @@ class StoryDialogComponent { content: this.i18nService.t("dialogContent"), type: "primary", acceptButtonText: "Ok", - cancelButtonText: null, + cancelButtonText: undefined, }, { title: this.i18nService.t("primaryTypeSimpleDialog"), @@ -123,7 +121,7 @@ class StoryDialogComponent { showCallout = false; calloutType = "info"; - dialogCloseResult: boolean; + dialogCloseResult?: boolean; constructor( public dialogService: DialogService, diff --git a/libs/components/src/form-field/bit-validators.stories.ts b/libs/components/src/form-field/bit-validators.stories.ts index df021256400..642ff30bb5a 100644 --- a/libs/components/src/form-field/bit-validators.stories.ts +++ b/libs/components/src/form-field/bit-validators.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { FormsModule, ReactiveFormsModule, FormBuilder } from "@angular/forms"; import { StoryObj, Meta, moduleMetadata } from "@storybook/angular"; @@ -51,7 +49,7 @@ const template = ` `; export const ForbiddenCharacters: StoryObj = { - render: (args: BitFormFieldComponent) => ({ + render: (args) => ({ props: { formObj: new FormBuilder().group({ name: ["", forbiddenCharacters(["\\", "/", "@", "#", "$", "%", "^", "&", "*", "(", ")"])], @@ -62,7 +60,7 @@ export const ForbiddenCharacters: StoryObj = { }; export const TrimValidator: StoryObj = { - render: (args: BitFormFieldComponent) => ({ + render: (args) => ({ props: { formObj: new FormBuilder().group({ name: [ diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index a02158655ee..ccd80d6fa75 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { TextFieldModule } from "@angular/cdk/text-field"; import { AbstractControl, @@ -190,7 +188,7 @@ export const Required: Story = { Label - + FormControl @@ -200,7 +198,7 @@ export const Required: Story = { }; export const Hint: Story = { - render: (args: BitFormFieldComponent) => ({ + render: (args) => ({ props: { formObj: formObj, ...args, @@ -268,7 +266,7 @@ export const Readonly: Story = { Textarea Premium @@ -361,7 +359,7 @@ export const PartiallyDisabledButtonInputGroup: Story = { }; export const Select: Story = { - render: (args: BitFormFieldComponent) => ({ + render: (args) => ({ props: args, template: /*html*/ ` @@ -377,7 +375,7 @@ export const Select: Story = { }; export const AdvancedSelect: Story = { - render: (args: BitFormFieldComponent) => ({ + render: (args) => ({ props: args, template: /*html*/ ` @@ -422,7 +420,7 @@ export const FileInput: Story = { }; export const Textarea: Story = { - render: (args: BitFormFieldComponent) => ({ + render: (args) => ({ props: args, template: /*html*/ ` diff --git a/libs/components/src/search/search.stories.ts b/libs/components/src/search/search.stories.ts index 71c180c6d51..a6cd714d43a 100644 --- a/libs/components/src/search/search.stories.ts +++ b/libs/components/src/search/search.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; @@ -34,7 +32,7 @@ export default { type Story = StoryObj; export const Default: Story = { - render: (args: SearchComponent) => ({ + render: (args) => ({ props: args, template: ` diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts index 2ca1c0fa952..382e19097b0 100644 --- a/libs/components/src/toast/toast.stories.ts +++ b/libs/components/src/toast/toast.stories.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @@ -24,7 +22,7 @@ const toastServiceExampleTemplate = ` }) export class ToastServiceExampleComponent { @Input() - toastOptions: ToastOptions; + toastOptions?: ToastOptions; constructor(protected toastService: ToastService) {} } @@ -40,7 +38,7 @@ export default { }), applicationConfig({ providers: [ - ToastModule.forRoot().providers, + ToastModule.forRoot().providers!, { provide: I18nService, useFactory: () => { From 966e8d3fb8a02b72ba2d404f06fc76356cfd24d6 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Tue, 7 Jan 2025 13:48:18 -0500 Subject: [PATCH 080/270] [PM-16667] Followup clarifying work (#12665) * clean up readability * fix ts-strict violations * fix consistency with uncertain cases in isCardExpired --- libs/common/src/autofill/utils.spec.ts | 2 +- libs/common/src/autofill/utils.ts | 60 ++++++++++++++++---------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/libs/common/src/autofill/utils.spec.ts b/libs/common/src/autofill/utils.spec.ts index 516a09e03d1..4dd36ba7d89 100644 --- a/libs/common/src/autofill/utils.spec.ts +++ b/libs/common/src/autofill/utils.spec.ts @@ -93,7 +93,7 @@ function getCardExpiryDateValues() { [undefined, undefined, false], // no month, no year, invalid values ["", "", false], // no month, no year, invalid values ["12", "agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", false], // invalid values - ["0", `${currentYear}`, true], // invalid month + ["0", `${currentYear}`, false], // invalid month ["0", `${currentYear - 1}`, true], // invalid 0 month ["00", `${currentYear + 1}`, false], // invalid 0 month [`${currentMonth}`, "0000", true], // current month, in the year 2000 diff --git a/libs/common/src/autofill/utils.ts b/libs/common/src/autofill/utils.ts index d9276cdbc8b..6bee5e1a198 100644 --- a/libs/common/src/autofill/utils.ts +++ b/libs/common/src/autofill/utils.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DelimiterPatternExpression, ExpiryFullYearPattern, @@ -25,11 +23,11 @@ export function normalizeExpiryYearFormat(yearInput: string | number): Year | nu let expirationYear = yearInputIsEmpty ? null : `${yearInput}`; // Exit early if year is already formatted correctly or empty - if (yearInputIsEmpty || /^[1-9]{1}\d{3}$/.test(expirationYear)) { + if (yearInputIsEmpty || (expirationYear && /^[1-9]{1}\d{3}$/.test(expirationYear))) { return expirationYear as Year; } - expirationYear = expirationYear + expirationYear = (expirationYear || "") // For safety, because even input[type="number"] will allow decimals .replace(/[^\d]/g, "") // remove any leading zero padding (leave the last leading zero if it ends the string) @@ -53,7 +51,7 @@ export function normalizeExpiryYearFormat(yearInput: string | number): Year | nu /** * Takes a cipher card view and returns "true" if the month and year affirmativey indicate - * the card is expired. + * the card is expired. Uncertain cases return "false". * * @param {CardView} cipherCard * @return {*} {boolean} @@ -62,30 +60,34 @@ export function isCardExpired(cipherCard: CardView): boolean { if (cipherCard) { const { expMonth = null, expYear = null } = cipherCard; + if (!expYear) { + return false; + } + const now = new Date(); const normalizedYear = normalizeExpiryYearFormat(expYear); - const parsedYear = parseInt(normalizedYear, 10); + const parsedYear = normalizedYear ? parseInt(normalizedYear, 10) : NaN; - const expiryYearIsBeforeThisYear = parsedYear < now.getFullYear(); - const expiryYearIsAfterThisYear = parsedYear > now.getFullYear(); + const expiryYearIsBeforeCurrentYear = parsedYear < now.getFullYear(); + const expiryYearIsAfterCurrentYear = parsedYear > now.getFullYear(); // If the expiry year is before the current year, skip checking the month, since it must be expired - if (normalizedYear && expiryYearIsBeforeThisYear) { + if (normalizedYear && expiryYearIsBeforeCurrentYear) { return true; } // If the expiry year is after the current year, skip checking the month, since it cannot be expired - if (normalizedYear && expiryYearIsAfterThisYear) { + if (normalizedYear && expiryYearIsAfterCurrentYear) { return false; } if (normalizedYear && expMonth) { const parsedMonthInteger = parseInt(expMonth, 10); - const parsedMonthIsInvalid = !parsedMonthInteger || isNaN(parsedMonthInteger); + const parsedMonthIsValid = parsedMonthInteger && !isNaN(parsedMonthInteger); - // If the parsed month value is 0, we don't know when the expiry passes this year, so treat it as expired - if (parsedMonthIsInvalid) { - return true; + // If the parsed month value is 0, we don't know when the expiry passes this year, so do not treat it as expired + if (!parsedMonthIsValid) { + return false; } // `Date` months are zero-indexed @@ -257,13 +259,18 @@ function parseNonDelimitedYearMonthExpiry(dateInput: string): [string | null, st parsedMonth = dateInput.slice(-1); const currentYear = new Date().getFullYear(); - const normalizedParsedYear = parseInt(normalizeExpiryYearFormat(parsedYear), 10); - const normalizedParsedYearAlternative = parseInt( - normalizeExpiryYearFormat(dateInput.slice(-2)), - 10, - ); + const normalizedYearFormat = normalizeExpiryYearFormat(parsedYear); + const normalizedParsedYear = normalizedYearFormat && parseInt(normalizedYearFormat, 10); + const normalizedExpiryYearFormat = normalizeExpiryYearFormat(dateInput.slice(-2)); + const normalizedParsedYearAlternative = + normalizedExpiryYearFormat && parseInt(normalizedExpiryYearFormat, 10); - if (normalizedParsedYear < currentYear && normalizedParsedYearAlternative >= currentYear) { + if ( + normalizedParsedYear && + normalizedParsedYear < currentYear && + normalizedParsedYearAlternative && + normalizedParsedYearAlternative >= currentYear + ) { parsedYear = dateInput.slice(-2); parsedMonth = dateInput.slice(0, 1); } @@ -295,17 +302,24 @@ export function parseYearMonthExpiry(combinedExpiryValue: string): [Year | null, // If there is only one date part, no delimiter was found in the passed value if (dateParts.length === 1) { - [parsedYear, parsedMonth] = parseNonDelimitedYearMonthExpiry(sanitizedFirstPart); + const [parsedNonDelimitedYear, parsedNonDelimitedMonth] = + parseNonDelimitedYearMonthExpiry(sanitizedFirstPart); + + parsedYear = parsedNonDelimitedYear; + parsedMonth = parsedNonDelimitedMonth; } // There are multiple date parts else { - [parsedYear, parsedMonth] = parseDelimitedYearMonthExpiry([ + const [parsedDelimitedYear, parsedDelimitedMonth] = parseDelimitedYearMonthExpiry([ sanitizedFirstPart, sanitizedSecondPart, ]); + + parsedYear = parsedDelimitedYear; + parsedMonth = parsedDelimitedMonth; } - const normalizedParsedYear = normalizeExpiryYearFormat(parsedYear); + const normalizedParsedYear = parsedYear ? normalizeExpiryYearFormat(parsedYear) : null; const normalizedParsedMonth = parsedMonth?.replace(/^0+/, "").slice(0, 2); // Set "empty" values to null From 02556c1416886828e9b7f7da5beb018dd44fae8c Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:09:37 +0100 Subject: [PATCH 081/270] Changes to restart cancelled org (#12730) --- .../change-plan-dialog.component.html | 22 +++- .../change-plan-dialog.component.ts | 118 +++++++++++++++++- .../organization-billing.module.ts | 2 - .../billing/services/trial-flow.service.ts | 83 ++++++++++-- .../components/vault-filter.component.ts | 38 +----- apps/web/src/locales/en/messages.json | 15 +++ .../billing-api.service.abstraction.ts | 6 + .../organization-billing.service.ts | 5 + .../organization-billing-metadata.response.ts | 2 + .../billing/services/billing-api.service.ts | 14 +++ .../services/organization-billing.service.ts | 13 ++ 11 files changed, 261 insertions(+), 57 deletions(-) diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 78005275f12..902cac9c771 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -1,16 +1,19 @@
    - {{ "upgradeFreeOrganization" | i18n: currentPlanName }} + {{ dialogHeaderName }}

    {{ "upgradePlans" | i18n }}

    - {{ "selectAPlan" | i18n }} + {{ + "selectAPlan" | i18n + }}
    +

    {{ "paymentMethod" | i18n }}

    -

    +

    {{ deprecateStripeSourcesAPI 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 9a80de555c6..d7ac442c40c 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 @@ -24,7 +24,14 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { + BillingApiServiceAbstraction, + BillingInformation, + OrganizationInformation, + PaymentInformation, + PlanInformation, + OrganizationBillingServiceAbstraction as OrganizationBillingService, +} from "@bitwarden/common/billing/abstractions"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { PaymentMethodType, @@ -49,6 +56,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +import { BillingSharedModule } from "../shared/billing-shared.module"; import { PaymentV2Component } from "../shared/payment/payment-v2.component"; import { PaymentComponent } from "../shared/payment/payment.component"; @@ -89,6 +97,8 @@ interface OnSuccessArgs { @Component({ templateUrl: "./change-plan-dialog.component.html", + standalone: true, + imports: [BillingSharedModule], }) export class ChangePlanDialogComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @@ -163,6 +173,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { organization: Organization; sub: OrganizationSubscriptionResponse; billing: BillingResponse; + dialogHeaderName: string; currentPlanName: string; showPayment: boolean = false; totalOpened: boolean = false; @@ -174,6 +185,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { paymentSource?: PaymentSourceResponse; deprecateStripeSourcesAPI: boolean; + isSubscriptionCanceled: boolean = false; private destroy$ = new Subject(); @@ -196,6 +208,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, private taxService: TaxServiceAbstraction, + private organizationBillingService: OrganizationBillingService, ) {} async ngOnInit(): Promise { @@ -208,6 +221,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.sub = this.dialogParams.subscription ?? (await this.organizationApiService.getSubscription(this.dialogParams.organizationId)); + this.dialogHeaderName = this.resolveHeaderName(this.sub); this.organizationId = this.dialogParams.organizationId; this.currentPlan = this.sub?.plan; this.selectedPlan = this.sub?.plan; @@ -281,6 +295,20 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.refreshSalesTax(); } + resolveHeaderName(subscription: OrganizationSubscriptionResponse): string { + if (subscription.subscription != null) { + this.isSubscriptionCanceled = subscription.subscription.cancelled; + if (subscription.subscription.cancelled) { + return this.i18nService.t("restartSubscription"); + } + } + + return this.i18nService.t( + "upgradeFreeOrganization", + this.resolvePlanName(this.dialogParams.productTierType), + ); + } + setInitialPlanSelection() { this.focusedIndex = this.selectableProducts.length - 1; this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); @@ -388,6 +416,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ]; } case PlanCardState.Disabled: { + if (this.isSubscriptionCanceled) { + return [ + "tw-cursor-not-allowed", + "tw-bg-secondary-100", + "tw-font-normal", + "tw-bg-blur", + "tw-text-muted", + "tw-block", + "tw-rounded", + "tw-w-80", + ]; + } + return [ "tw-cursor-not-allowed", "tw-bg-secondary-100", @@ -409,7 +450,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return; } - if (plan === this.currentPlan) { + if (plan === this.currentPlan && !this.isSubscriptionCanceled) { return; } this.selectedPlan = plan; @@ -446,6 +487,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get selectableProducts() { + if (this.isSubscriptionCanceled) { + // Return only the current plan if the subscription is canceled + return [this.currentPlan]; + } + if (this.acceptingSponsorship) { const familyPlan = this.passwordManagerPlans.find( (plan) => plan.type === PlanType.FamiliesAnnually, @@ -692,11 +738,18 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const doSubmit = async (): Promise => { let orgId: string = null; - orgId = await this.updateOrganization(); + if (this.isSubscriptionCanceled) { + await this.restartSubscription(); + orgId = this.organizationId; + } else { + orgId = await this.updateOrganization(); + } this.toastService.showToast({ variant: "success", title: null, - message: this.i18nService.t("organizationUpgraded"), + message: this.isSubscriptionCanceled + ? this.i18nService.t("restartOrganizationSubscription") + : this.i18nService.t("organizationUpgraded"), }); await this.apiService.refreshIdentityToken(); @@ -726,6 +779,44 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.dialogRef.close(); }; + private async restartSubscription() { + const org = await this.organizationApiService.get(this.organizationId); + const organization: OrganizationInformation = { + name: org.name, + billingEmail: org.billingEmail, + }; + + const plan: PlanInformation = { + type: this.selectedPlan.type, + passwordManagerSeats: org.seats, + }; + + if (org.useSecretsManager) { + plan.subscribeToSecretsManager = true; + plan.secretsManagerSeats = org.smSeats; + } + + let paymentMethod: [string, PaymentMethodType]; + + if (this.deprecateStripeSourcesAPI) { + const { type, token } = await this.paymentV2Component.tokenize(); + paymentMethod = [token, type]; + } else { + paymentMethod = await this.paymentComponent.createPaymentToken(); + } + + const payment: PaymentInformation = { + paymentMethod, + billing: this.getBillingInformationFromTaxInfoComponent(), + }; + + await this.organizationBillingService.restartSubscription(this.organization.id, { + organization, + plan, + payment, + }); + } + private async updateOrganization() { const request = new OrganizationUpgradeRequest(); if (this.selectedPlan.productTier !== ProductTierType.Families) { @@ -802,6 +893,18 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return text; } + private getBillingInformationFromTaxInfoComponent(): BillingInformation { + return { + country: this.taxInformation.country, + postalCode: this.taxInformation.postalCode, + taxId: this.taxInformation.taxId, + addressLine1: this.taxInformation.line1, + addressLine2: this.taxInformation.line2, + city: this.taxInformation.city, + state: this.taxInformation.state, + }; + } + private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void { request.useSecretsManager = this.organization.useSecretsManager; if (!this.organization.useSecretsManager) { @@ -997,6 +1100,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } protected canUpdatePaymentInformation(): boolean { - return this.upgradeRequiresPaymentMethod || this.showPayment || this.isPaymentSourceEmpty(); + return ( + this.upgradeRequiresPaymentMethod || + this.showPayment || + this.isPaymentSourceEmpty() || + this.isSubscriptionCanceled + ); } } diff --git a/apps/web/src/app/billing/organizations/organization-billing.module.ts b/apps/web/src/app/billing/organizations/organization-billing.module.ts index b25cda662f2..48ac613711d 100644 --- a/apps/web/src/app/billing/organizations/organization-billing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing.module.ts @@ -8,7 +8,6 @@ import { BillingSharedModule } from "../shared"; import { AdjustSubscription } from "./adjust-subscription.component"; import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; import { BillingSyncKeyComponent } from "./billing-sync-key.component"; -import { ChangePlanDialogComponent } from "./change-plan-dialog.component"; import { ChangePlanComponent } from "./change-plan.component"; import { DownloadLicenceDialogComponent } from "./download-license.component"; import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; @@ -44,7 +43,6 @@ import { SubscriptionStatusComponent } from "./subscription-status.component"; SecretsManagerSubscribeStandaloneComponent, SubscriptionHiddenComponent, SubscriptionStatusComponent, - ChangePlanDialogComponent, OrganizationPaymentMethodComponent, ], }) 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 558851ad64c..a3a4ba6bba1 100644 --- a/apps/web/src/app/billing/services/trial-flow.service.ts +++ b/apps/web/src/app/billing/services/trial-flow.service.ts @@ -2,25 +2,37 @@ // @ts-strict-ignore import { Injectable } from "@angular/core"; import { Router } from "@angular/router"; +import { lastValueFrom } from "rxjs"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.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"; import { FreeTrial } from "../../core/types/free-trial"; +import { + ChangePlanDialogResultType, + openChangePlanDialog, +} from "../organizations/change-plan-dialog.component"; @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, @@ -66,16 +78,31 @@ export class TrialFlowService { org: Organization, organizationBillingMetadata: OrganizationBillingMetadataResponse, ): Promise { - if (organizationBillingMetadata.isSubscriptionUnpaid) { - const confirmed = await this.promptForPaymentNavigation(org); + if ( + organizationBillingMetadata.isSubscriptionUnpaid || + organizationBillingMetadata.isSubscriptionCanceled + ) { + const confirmed = await this.promptForPaymentNavigation( + org, + organizationBillingMetadata.isSubscriptionCanceled, + organizationBillingMetadata.isSubscriptionUnpaid, + ); if (confirmed) { await this.navigateToPaymentMethod(org?.id); } } } - private async promptForPaymentNavigation(org: Organization): Promise { - if (!org?.isOwner) { + private async promptForPaymentNavigation( + org: Organization, + 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), content: { key: "suspendedUserOrgMessage" }, @@ -85,13 +112,31 @@ export class TrialFlowService { }); return false; } - return await this.dialogService.openSimpleDialog({ - title: this.i18nService.t("suspendedOrganizationTitle", org?.name), - content: { key: "suspendedOwnerOrgMessage" }, - type: "danger", - acceptButtonText: this.i18nService.t("continue"), - cancelButtonText: this.i18nService.t("close"), - }); + + if (org.providerId && this.resellerManagedOrgAlert) { + await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("suspendedOrganizationTitle", org.name), + content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] }, + type: "danger", + acceptButtonText: this.i18nService.t("close"), + cancelButtonText: null, + }); + return false; + } + + if (org.isOwner && isUnpaid) { + return await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("suspendedOrganizationTitle", org.name), + content: { key: "suspendedOwnerOrgMessage" }, + type: "danger", + acceptButtonText: this.i18nService.t("continue"), + cancelButtonText: this.i18nService.t("close"), + }); + } + + if (org.isOwner && isCanceled && this.resellerManagedOrgAlert) { + await this.changePlan(org); + } } private async navigateToPaymentMethod(orgId: string) { @@ -99,4 +144,20 @@ export class TrialFlowService { state: { launchPaymentModalAutomatically: true }, }); } + + private async changePlan(org: Organization) { + const subscription = await this.organizationApiService.getSubscription(org.id); + const reference = openChangePlanDialog(this.dialogService, { + data: { + organizationId: org.id, + subscription: subscription, + productTierType: org.productTierType, + }, + }); + + const result = await lastValueFrom(reference.closed); + if (result === ChangePlanDialogResultType.Closed) { + return; + } + } } 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 efb1754c811..f568ba159a6 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 @@ -6,7 +6,6 @@ import { firstValueFrom, Subject } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -16,6 +15,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { DialogService } from "@bitwarden/components"; +import { TrialFlowService } from "../../../../billing/services/trial-flow.service"; import { VaultFilterService } from "../services/abstractions/vault-filter.service"; import { VaultFilterList, @@ -91,6 +91,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy { return "searchVault"; } + private trialFlowService = inject(TrialFlowService); + constructor( protected vaultFilterService: VaultFilterService, protected policyService: PolicyService, @@ -126,13 +128,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { this.i18nService.t("disabledOrganizationFilterError"), ); const metadata = await this.billingApiService.getOrganizationBillingMetadata(orgNode.node.id); - if (metadata.isSubscriptionUnpaid) { - const confirmed = await this.promptForPaymentNavigation(orgNode.node); - if (confirmed) { - await this.navigateToPaymentMethod(orgNode.node.id); - } - } - return; + await this.trialFlowService.handleUnpaidSubscriptionDialog(orgNode.node, metadata); } const filter = this.activeFilter; if (orgNode?.node.id === "AllVaults") { @@ -144,32 +140,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy { await this.vaultFilterService.expandOrgFilter(); }; - private async promptForPaymentNavigation(org: Organization): Promise { - if (!org?.isOwner) { - await this.dialogService.openSimpleDialog({ - title: this.i18nService.t("suspendedOrganizationTitle", org?.name), - content: { key: "suspendedUserOrgMessage" }, - type: "danger", - acceptButtonText: this.i18nService.t("close"), - cancelButtonText: null, - }); - return false; - } - return await this.dialogService.openSimpleDialog({ - title: this.i18nService.t("suspendedOrganizationTitle", org?.name), - content: { key: "suspendedOwnerOrgMessage" }, - type: "danger", - acceptButtonText: this.i18nService.t("continue"), - cancelButtonText: this.i18nService.t("close"), - }); - } - - private async navigateToPaymentMethod(orgId: string) { - await this.router.navigate(["organizations", `${orgId}`, "billing", "payment-method"], { - state: { launchPaymentModalAutomatically: true }, - }); - } - applyTypeFilter = async (filterNode: TreeNode): Promise => { const filter = this.activeFilter; filter.resetFilter(); diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 08e08ccad15..b0cbd050c13 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10066,5 +10066,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index 8b82795fb50..4b08b52a136 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -8,6 +8,7 @@ import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/reque import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; +import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; @@ -74,4 +75,9 @@ export abstract class BillingApiServiceAbstraction { organizationId: string, request: VerifyBankAccountRequest, ) => Promise; + + restartSubscription: ( + organizationId: string, + request: OrganizationCreateRequest, + ) => Promise; } diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index ddcd61573a6..7c4e0a39f8f 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -57,4 +57,9 @@ export abstract class OrganizationBillingServiceAbstraction { ) => Promise; startFree: (subscription: SubscriptionInformation) => Promise; + + restartSubscription: ( + organizationId: string, + subscription: SubscriptionInformation, + ) => Promise; } diff --git a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts index c5023cb64c1..d30ad76a147 100644 --- a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts +++ b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts @@ -10,6 +10,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { invoiceDueDate: Date | null; invoiceCreatedDate: Date | null; subPeriodEndDate: Date | null; + isSubscriptionCanceled: boolean; constructor(response: any) { super(response); @@ -23,6 +24,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { this.invoiceDueDate = this.parseDate(this.getResponseProperty("InvoiceDueDate")); this.invoiceCreatedDate = this.parseDate(this.getResponseProperty("InvoiceCreatedDate")); this.subPeriodEndDate = this.parseDate(this.getResponseProperty("SubPeriodEndDate")); + this.isSubscriptionCanceled = this.getResponseProperty("IsSubscriptionCanceled"); } private parseDate(dateString: any): Date | null { diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index cb69f294409..7ce5602f3cc 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -10,6 +10,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { ToastService } from "@bitwarden/components"; import { ApiService } from "../../abstractions/api.service"; +import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { BillingApiServiceAbstraction } from "../../billing/abstractions"; import { PaymentMethodType } from "../../billing/enums"; import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request"; @@ -214,6 +215,19 @@ export class BillingApiService implements BillingApiServiceAbstraction { ); } + async restartSubscription( + organizationId: string, + request: OrganizationCreateRequest, + ): Promise { + return await this.apiService.send( + "POST", + "/organizations/" + organizationId + "/billing/restart-subscription", + request, + true, + false, + ); + } + private async execute(request: () => Promise): Promise { try { return await request(); diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index a0b3782f1ad..ca10b368662 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -223,4 +223,17 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs request.additionalStorageGb = information.storage; } } + + async restartSubscription( + organizationId: string, + subscription: SubscriptionInformation, + ): Promise { + const request = new OrganizationCreateRequest(); + const organizationKeys = await this.makeOrganizationKeys(); + this.setOrganizationKeys(request, organizationKeys); + this.setOrganizationInformation(request, subscription.organization); + this.setPlanInformation(request, subscription.plan); + this.setPaymentInformation(request, subscription.payment); + await this.billingApiService.restartSubscription(organizationId, request); + } } From f99a3c41627db44ffe573e091b3a35fbf091359a Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:29:36 -0600 Subject: [PATCH 082/270] feat(web): [PM-1214] add device management screen Adds a device management tab under settings -> security that allows users to: - View and manage their account's connected devices - Remove/deactivate devices - See device details like platform, last login, and trust status - Sort and filter device list with virtual scrolling Resolves PM-1214 --- .../browser/src/background/main.background.ts | 5 +- .../security/device-management.component.html | 88 +++++++ .../security/device-management.component.ts | 220 ++++++++++++++++++ .../security/security-routing.module.ts | 6 + .../settings/security/security.component.html | 1 + .../settings/security/security.component.ts | 6 +- apps/web/src/locales/en/messages.json | 39 ++++ .../src/services/jslib-services.module.ts | 2 +- .../devices-api.service.abstraction.ts | 6 + .../devices/devices.service.abstraction.ts | 15 +- .../devices/responses/device.response.ts | 5 + .../abstractions/devices/views/device.view.ts | 1 + .../request/update-devices-trust.request.ts | 4 +- ...devices-api.service.implementation.spec.ts | 100 ++++++++ .../devices-api.service.implementation.ts | 4 + .../devices/devices.service.implementation.ts | 24 +- libs/common/src/enums/device-type.enum.ts | 50 ++-- 17 files changed, 549 insertions(+), 27 deletions(-) create mode 100644 apps/web/src/app/auth/settings/security/device-management.component.html create mode 100644 apps/web/src/app/auth/settings/security/device-management.component.ts create mode 100644 libs/common/src/auth/services/devices-api.service.implementation.spec.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index ff240ec8cac..bcfa797e0ff 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -769,7 +769,10 @@ export default class MainBackground { this.configService, ); - this.devicesService = new DevicesServiceImplementation(this.devicesApiService); + this.devicesService = new DevicesServiceImplementation( + this.devicesApiService, + this.appIdService, + ); this.authRequestService = new AuthRequestService( this.appIdService, diff --git a/apps/web/src/app/auth/settings/security/device-management.component.html b/apps/web/src/app/auth/settings/security/device-management.component.html new file mode 100644 index 00000000000..6bae88fac51 --- /dev/null +++ b/apps/web/src/app/auth/settings/security/device-management.component.html @@ -0,0 +1,88 @@ + +

    +
    +

    {{ "devices" | i18n }}

    + + +

    {{ "aDeviceIs" | i18n }}

    +
    + +
    +
    + +

    {{ "deviceListDescription" | i18n }}

    + +
    + +
    + + + + + {{ col.title }} + + + + + +
    + +
    +
    + {{ row.displayName }} + + {{ "trusted" | i18n }} + +
    + + + {{ + "currentSession" | i18n + }} + {{ + "requestPending" | i18n + }} + + {{ row.firstLogin | date: "medium" }} + + + + + + +
    +
    + diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts new file mode 100644 index 00000000000..65f2afc250e --- /dev/null +++ b/apps/web/src/app/auth/settings/security/device-management.component.ts @@ -0,0 +1,220 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { firstValueFrom } from "rxjs"; +import { switchMap } from "rxjs/operators"; + +import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; +import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; +import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { + DialogService, + ToastService, + TableDataSource, + TableModule, + PopoverModule, +} from "@bitwarden/components"; + +import { SharedModule } from "../../../shared"; + +interface DeviceTableData { + id: string; + type: DeviceType; + displayName: string; + loginStatus: string; + firstLogin: Date; + trusted: boolean; + devicePendingAuthRequest: object | null; +} + +/** + * Provides a table of devices and allows the user to log out, approve or remove a device + */ +@Component({ + selector: "app-device-management", + templateUrl: "./device-management.component.html", + standalone: true, + imports: [CommonModule, SharedModule, TableModule, PopoverModule], +}) +export class DeviceManagementComponent { + protected readonly tableId = "device-management-table"; + protected dataSource = new TableDataSource(); + protected currentDevice: DeviceView | undefined; + protected loading = true; + protected asyncActionLoading = false; + + constructor( + private i18nService: I18nService, + private devicesService: DevicesServiceAbstraction, + private dialogService: DialogService, + private toastService: ToastService, + private validationService: ValidationService, + ) { + this.devicesService + .getCurrentDevice$() + .pipe( + takeUntilDestroyed(), + switchMap((currentDevice) => { + this.currentDevice = new DeviceView(currentDevice); + return this.devicesService.getDevices$(); + }), + ) + .subscribe({ + next: (devices) => { + this.dataSource.data = devices.map((device) => { + return { + id: device.id, + type: device.type, + displayName: this.getHumanReadableDeviceType(device.type), + loginStatus: this.getLoginStatus(device), + devicePendingAuthRequest: device.response.devicePendingAuthRequest, + firstLogin: new Date(device.creationDate), + trusted: device.response.isTrusted, + }; + }); + this.loading = false; + }, + error: () => { + this.loading = false; + }, + }); + } + + /** + * Column configuration for the table + */ + protected readonly columnConfig = [ + { + name: "displayName", + title: this.i18nService.t("device"), + headerClass: "tw-w-1/3", + sortable: true, + }, + { + name: "loginStatus", + title: this.i18nService.t("loginStatus"), + headerClass: "tw-w-1/3", + sortable: true, + }, + { + name: "firstLogin", + title: this.i18nService.t("firstLogin"), + headerClass: "tw-w-1/3", + sortable: true, + }, + ]; + + /** + * Get the icon for a device type + * @param type - The device type + * @returns The icon for the device type + */ + getDeviceIcon(type: DeviceType): string { + const defaultIcon = "bwi bwi-desktop"; + const categoryIconMap: Record = { + webVault: "bwi bwi-browser", + desktop: "bwi bwi-desktop", + mobile: "bwi bwi-mobile", + cli: "bwi bwi-cli", + extension: "bwi bwi-puzzle", + sdk: "bwi bwi-desktop", + }; + + const metadata = DeviceTypeMetadata[type]; + return metadata ? (categoryIconMap[metadata.category] ?? defaultIcon) : defaultIcon; + } + + /** + * Get the login status of a device + * It will return the current session if the device is the current device + * It will return the date of the pending auth request when available + * @param device - The device + * @returns The login status + */ + private getLoginStatus(device: DeviceView): string { + if (this.isCurrentDevice(device)) { + return this.i18nService.t("currentSession"); + } + + if (device.response.devicePendingAuthRequest?.creationDate) { + return this.i18nService.t("requestPending"); + } + + return ""; + } + + /** + * Get a human readable device type from the DeviceType enum + * @param type - The device type + * @returns The human readable device type + */ + private getHumanReadableDeviceType(type: DeviceType): string { + const metadata = DeviceTypeMetadata[type]; + if (!metadata) { + return this.i18nService.t("unknownDevice"); + } + + // If the platform is "Unknown" translate it since it is not a proper noun + const platform = + metadata.platform === "Unknown" ? this.i18nService.t("unknown") : metadata.platform; + const category = this.i18nService.t(metadata.category); + return platform ? `${category} - ${platform}` : category; + } + + /** + * Check if a device is the current device + * @param device - The device or device table data + * @returns True if the device is the current device, false otherwise + */ + protected isCurrentDevice(device: DeviceView | DeviceTableData): boolean { + return "response" in device + ? device.id === this.currentDevice?.id + : device.id === this.currentDevice?.id; + } + + /** + * Check if a device has a pending auth request + * @param device - The device + * @returns True if the device has a pending auth request, false otherwise + */ + protected hasPendingAuthRequest(device: DeviceTableData): boolean { + return ( + device.devicePendingAuthRequest !== undefined && device.devicePendingAuthRequest !== null + ); + } + + /** + * Remove a device + * @param device - The device + */ + protected async removeDevice(device: DeviceTableData) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "removeDevice" }, + content: { key: "removeDeviceConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + this.asyncActionLoading = true; + await firstValueFrom(this.devicesService.deactivateDevice$(device.id)); + this.asyncActionLoading = false; + + // Remove the device from the data source + this.dataSource.data = this.dataSource.data.filter((d) => d.id !== device.id); + + this.toastService.showToast({ + title: "", + message: this.i18nService.t("deviceRemoved"), + variant: "success", + }); + } catch (error) { + this.validationService.showError(error); + } + } +} diff --git a/apps/web/src/app/auth/settings/security/security-routing.module.ts b/apps/web/src/app/auth/settings/security/security-routing.module.ts index 8af0499d05a..6ed21605184 100644 --- a/apps/web/src/app/auth/settings/security/security-routing.module.ts +++ b/apps/web/src/app/auth/settings/security/security-routing.module.ts @@ -4,6 +4,7 @@ import { RouterModule, Routes } from "@angular/router"; import { ChangePasswordComponent } from "../change-password.component"; import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component"; +import { DeviceManagementComponent } from "./device-management.component"; import { SecurityKeysComponent } from "./security-keys.component"; import { SecurityComponent } from "./security.component"; @@ -29,6 +30,11 @@ const routes: Routes = [ component: SecurityKeysComponent, data: { titleId: "keys" }, }, + { + path: "device-management", + component: DeviceManagementComponent, + data: { titleId: "devices" }, + }, ], }, ]; diff --git a/apps/web/src/app/auth/settings/security/security.component.html b/apps/web/src/app/auth/settings/security/security.component.html index 25459faeacc..6bd7c1daf36 100644 --- a/apps/web/src/app/auth/settings/security/security.component.html +++ b/apps/web/src/app/auth/settings/security/security.component.html @@ -4,6 +4,7 @@ {{ "masterPassword" | i18n }}
    {{ "twoStepLogin" | i18n }} + {{ "devices" | i18n }} {{ "keys" | i18n }} diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 1df8145a917..d643b565df2 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @Component({ selector: "app-security", @@ -9,7 +10,10 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use export class SecurityComponent implements OnInit { showChangePassword = true; - constructor(private userVerificationService: UserVerificationService) {} + constructor( + private userVerificationService: UserVerificationService, + private configService: ConfigService, + ) {} async ngOnInit() { this.showChangePassword = await this.userVerificationService.hasMasterPassword(); diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b0cbd050c13..45aa1c34234 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3765,6 +3777,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8236,6 +8257,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9939,6 +9972,12 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, "claimedDomains": { "message": "Claimed domains" }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index f9a72f24476..d990a7315f2 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1109,7 +1109,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: DevicesServiceAbstraction, useClass: DevicesServiceImplementation, - deps: [DevicesApiServiceAbstraction], + deps: [DevicesApiServiceAbstraction, AppIdServiceAbstraction], }), safeProvider({ provide: DeviceTrustServiceAbstraction, diff --git a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts index 0af89928449..92f0ebf1667 100644 --- a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts @@ -36,4 +36,10 @@ export abstract class DevicesApiServiceAbstraction { * @param deviceIdentifier - current device identifier */ postDeviceTrustLoss: (deviceIdentifier: string) => Promise; + + /** + * Deactivates a device + * @param deviceId - The device ID + */ + deactivateDevice: (deviceId: string) => Promise; } diff --git a/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts b/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts index a02ccc64876..ba6890947c1 100644 --- a/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/devices/devices.service.abstraction.ts @@ -1,17 +1,18 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Observable } from "rxjs"; +import { DeviceResponse } from "./responses/device.response"; import { DeviceView } from "./views/device.view"; export abstract class DevicesServiceAbstraction { - getDevices$: () => Observable>; - getDeviceByIdentifier$: (deviceIdentifier: string) => Observable; - isDeviceKnownForUser$: (email: string, deviceIdentifier: string) => Observable; - updateTrustedDeviceKeys$: ( + abstract getDevices$(): Observable>; + abstract getDeviceByIdentifier$(deviceIdentifier: string): Observable; + abstract isDeviceKnownForUser$(email: string, deviceIdentifier: string): Observable; + abstract updateTrustedDeviceKeys$( deviceIdentifier: string, devicePublicKeyEncryptedUserKey: string, userKeyEncryptedDevicePublicKey: string, deviceKeyEncryptedDevicePrivateKey: string, - ) => Observable; + ): Observable; + abstract deactivateDevice$(deviceId: string): Observable; + abstract getCurrentDevice$(): Observable; } diff --git a/libs/common/src/auth/abstractions/devices/responses/device.response.ts b/libs/common/src/auth/abstractions/devices/responses/device.response.ts index a4e40037b05..707616744ad 100644 --- a/libs/common/src/auth/abstractions/devices/responses/device.response.ts +++ b/libs/common/src/auth/abstractions/devices/responses/device.response.ts @@ -9,6 +9,9 @@ export class DeviceResponse extends BaseResponse { type: DeviceType; creationDate: string; revisionDate: string; + isTrusted: boolean; + devicePendingAuthRequest: { id: string; creationDate: string } | null; + constructor(response: any) { super(response); this.id = this.getResponseProperty("Id"); @@ -18,5 +21,7 @@ export class DeviceResponse extends BaseResponse { this.type = this.getResponseProperty("Type"); this.creationDate = this.getResponseProperty("CreationDate"); this.revisionDate = this.getResponseProperty("RevisionDate"); + this.isTrusted = this.getResponseProperty("IsTrusted"); + this.devicePendingAuthRequest = this.getResponseProperty("DevicePendingAuthRequest"); } } diff --git a/libs/common/src/auth/abstractions/devices/views/device.view.ts b/libs/common/src/auth/abstractions/devices/views/device.view.ts index a901eb998b4..22e522b9eb0 100644 --- a/libs/common/src/auth/abstractions/devices/views/device.view.ts +++ b/libs/common/src/auth/abstractions/devices/views/device.view.ts @@ -12,6 +12,7 @@ export class DeviceView implements View { type: DeviceType; creationDate: string; revisionDate: string; + response: DeviceResponse; constructor(deviceResponse: DeviceResponse) { Object.assign(this, deviceResponse); diff --git a/libs/common/src/auth/models/request/update-devices-trust.request.ts b/libs/common/src/auth/models/request/update-devices-trust.request.ts index 8e3ce86c1a9..21fe0f600dc 100644 --- a/libs/common/src/auth/models/request/update-devices-trust.request.ts +++ b/libs/common/src/auth/models/request/update-devices-trust.request.ts @@ -8,8 +8,8 @@ export class UpdateDevicesTrustRequest extends SecretVerificationRequest { } export class DeviceKeysUpdateRequest { - encryptedPublicKey: string; - encryptedUserKey: string; + encryptedPublicKey: string | undefined; + encryptedUserKey: string | undefined; } export class OtherDeviceKeysUpdateRequest extends DeviceKeysUpdateRequest { diff --git a/libs/common/src/auth/services/devices-api.service.implementation.spec.ts b/libs/common/src/auth/services/devices-api.service.implementation.spec.ts new file mode 100644 index 00000000000..7aea36c7bd4 --- /dev/null +++ b/libs/common/src/auth/services/devices-api.service.implementation.spec.ts @@ -0,0 +1,100 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "../../abstractions/api.service"; +import { DeviceResponse } from "../abstractions/devices/responses/device.response"; + +import { DevicesApiServiceImplementation } from "./devices-api.service.implementation"; + +describe("DevicesApiServiceImplementation", () => { + let devicesApiService: DevicesApiServiceImplementation; + let apiService: MockProxy; + + beforeEach(() => { + apiService = mock(); + devicesApiService = new DevicesApiServiceImplementation(apiService); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("getKnownDevice", () => { + it("calls api with correct parameters", async () => { + const email = "test@example.com"; + const deviceIdentifier = "device123"; + apiService.send.mockResolvedValue(true); + + const result = await devicesApiService.getKnownDevice(email, deviceIdentifier); + + expect(result).toBe(true); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + "/devices/knowndevice", + null, + false, + true, + null, + expect.any(Function), + ); + }); + }); + + describe("getDeviceByIdentifier", () => { + it("returns device response", async () => { + const deviceIdentifier = "device123"; + const mockResponse = { id: "123", name: "Test Device" }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await devicesApiService.getDeviceByIdentifier(deviceIdentifier); + + expect(result).toBeInstanceOf(DeviceResponse); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/devices/identifier/${deviceIdentifier}`, + null, + true, + true, + ); + }); + }); + + describe("updateTrustedDeviceKeys", () => { + it("updates device keys and returns device response", async () => { + const deviceIdentifier = "device123"; + const publicKeyEncrypted = "encryptedPublicKey"; + const userKeyEncrypted = "encryptedUserKey"; + const deviceKeyEncrypted = "encryptedDeviceKey"; + const mockResponse = { id: "123", name: "Test Device" }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await devicesApiService.updateTrustedDeviceKeys( + deviceIdentifier, + publicKeyEncrypted, + userKeyEncrypted, + deviceKeyEncrypted, + ); + + expect(result).toBeInstanceOf(DeviceResponse); + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + `/devices/${deviceIdentifier}/keys`, + { + encryptedPrivateKey: deviceKeyEncrypted, + encryptedPublicKey: userKeyEncrypted, + encryptedUserKey: publicKeyEncrypted, + }, + true, + true, + ); + }); + }); + + describe("error handling", () => { + it("propagates api errors", async () => { + const error = new Error("API Error"); + apiService.send.mockRejectedValue(error); + + await expect(devicesApiService.getDevices()).rejects.toThrow("API Error"); + }); + }); +}); diff --git a/libs/common/src/auth/services/devices-api.service.implementation.ts b/libs/common/src/auth/services/devices-api.service.implementation.ts index 711f0bb68ec..cf760effbdf 100644 --- a/libs/common/src/auth/services/devices-api.service.implementation.ts +++ b/libs/common/src/auth/services/devices-api.service.implementation.ts @@ -117,4 +117,8 @@ export class DevicesApiServiceImplementation implements DevicesApiServiceAbstrac }, ); } + + async deactivateDevice(deviceId: string): Promise { + await this.apiService.send("POST", `/devices/${deviceId}/deactivate`, null, true, false); + } } diff --git a/libs/common/src/auth/services/devices/devices.service.implementation.ts b/libs/common/src/auth/services/devices/devices.service.implementation.ts index 6032ed66a89..cd6f1148dd8 100644 --- a/libs/common/src/auth/services/devices/devices.service.implementation.ts +++ b/libs/common/src/auth/services/devices/devices.service.implementation.ts @@ -1,5 +1,7 @@ import { Observable, defer, map } from "rxjs"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; + import { ListResponse } from "../../../models/response/list.response"; import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction"; import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; @@ -15,7 +17,10 @@ import { DevicesApiServiceAbstraction } from "../../abstractions/devices-api.ser * (i.e., promsise --> observables are cold until subscribed to) */ export class DevicesServiceImplementation implements DevicesServiceAbstraction { - constructor(private devicesApiService: DevicesApiServiceAbstraction) {} + constructor( + private devicesApiService: DevicesApiServiceAbstraction, + private appIdService: AppIdService, + ) {} /** * @description Gets the list of all devices. @@ -65,4 +70,21 @@ export class DevicesServiceImplementation implements DevicesServiceAbstraction { ), ).pipe(map((deviceResponse: DeviceResponse) => new DeviceView(deviceResponse))); } + + /** + * @description Deactivates a device + */ + deactivateDevice$(deviceId: string): Observable { + return defer(() => this.devicesApiService.deactivateDevice(deviceId)); + } + + /** + * @description Gets the current device. + */ + getCurrentDevice$(): Observable { + return defer(async () => { + const deviceIdentifier = await this.appIdService.getAppId(); + return this.devicesApiService.getDeviceByIdentifier(deviceIdentifier); + }); + } } diff --git a/libs/common/src/enums/device-type.enum.ts b/libs/common/src/enums/device-type.enum.ts index 1b8574a4c49..ff6329b9ac4 100644 --- a/libs/common/src/enums/device-type.enum.ts +++ b/libs/common/src/enums/device-type.enum.ts @@ -27,18 +27,40 @@ export enum DeviceType { LinuxCLI = 25, } -export const MobileDeviceTypes: Set = new Set([ - DeviceType.Android, - DeviceType.iOS, - DeviceType.AndroidAmazon, -]); +/** + * Device type metadata + * Each device type has a category corresponding to the client type and platform (Android, iOS, Chrome, Firefox, etc.) + */ +interface DeviceTypeMetadata { + category: "mobile" | "extension" | "webVault" | "desktop" | "cli" | "sdk" | "server"; + platform: string; +} -export const DesktopDeviceTypes: Set = new Set([ - DeviceType.WindowsDesktop, - DeviceType.MacOsDesktop, - DeviceType.LinuxDesktop, - DeviceType.UWP, - DeviceType.WindowsCLI, - DeviceType.MacOsCLI, - DeviceType.LinuxCLI, -]); +export const DeviceTypeMetadata: Record = { + [DeviceType.Android]: { category: "mobile", platform: "Android" }, + [DeviceType.iOS]: { category: "mobile", platform: "iOS" }, + [DeviceType.AndroidAmazon]: { category: "mobile", platform: "Amazon" }, + [DeviceType.ChromeExtension]: { category: "extension", platform: "Chrome" }, + [DeviceType.FirefoxExtension]: { category: "extension", platform: "Firefox" }, + [DeviceType.OperaExtension]: { category: "extension", platform: "Opera" }, + [DeviceType.EdgeExtension]: { category: "extension", platform: "Edge" }, + [DeviceType.VivaldiExtension]: { category: "extension", platform: "Vivaldi" }, + [DeviceType.SafariExtension]: { category: "extension", platform: "Safari" }, + [DeviceType.ChromeBrowser]: { category: "webVault", platform: "Chrome" }, + [DeviceType.FirefoxBrowser]: { category: "webVault", platform: "Firefox" }, + [DeviceType.OperaBrowser]: { category: "webVault", platform: "Opera" }, + [DeviceType.EdgeBrowser]: { category: "webVault", platform: "Edge" }, + [DeviceType.IEBrowser]: { category: "webVault", platform: "IE" }, + [DeviceType.SafariBrowser]: { category: "webVault", platform: "Safari" }, + [DeviceType.VivaldiBrowser]: { category: "webVault", platform: "Vivaldi" }, + [DeviceType.UnknownBrowser]: { category: "webVault", platform: "Unknown" }, + [DeviceType.WindowsDesktop]: { category: "desktop", platform: "Windows" }, + [DeviceType.MacOsDesktop]: { category: "desktop", platform: "macOS" }, + [DeviceType.LinuxDesktop]: { category: "desktop", platform: "Linux" }, + [DeviceType.UWP]: { category: "desktop", platform: "Windows UWP" }, + [DeviceType.WindowsCLI]: { category: "cli", platform: "Windows" }, + [DeviceType.MacOsCLI]: { category: "cli", platform: "macOS" }, + [DeviceType.LinuxCLI]: { category: "cli", platform: "Linux" }, + [DeviceType.SDK]: { category: "sdk", platform: "" }, + [DeviceType.Server]: { category: "server", platform: "" }, +}; From dbed5ff79b452501c561fe7f47b294221171f733 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Tue, 7 Jan 2025 15:09:43 -0500 Subject: [PATCH 083/270] [PM-16247] Autofill base common content components (#12668) * PoC implementation * build notification header components * use emotion css, and add button row components * add icons * update close button component to use new icon * add cipher components * reorganize notification component to accomodate body overflow with static footer * add action row component and fix overflow cases * fix component directory casings * add scrollbar styles * fix edit button icon display size issue * fix edit button interaction * cleanup and add dropdown menu buttons * fix footer display of full-width children * use svg brand icon in header component * refine body and footer overflow layout handling * fix fallback cipher icon sizing and other cleanup * component restructure and cleanup * restructure icon components * cleanup * re-org notification body and footer components and add typing * additional cleanup --- .../components/buttons/action-button.ts | 66 +++++ .../components/buttons/badge-button.ts | 67 +++++ .../components/buttons/close-button.ts | 39 +++ .../content/components/buttons/edit-button.ts | 60 ++++ .../components/cipher/cipher-action.ts | 31 ++ .../content/components/cipher/cipher-icon.ts | 33 +++ .../cipher/cipher-indicator-icons.ts | 35 +++ .../content/components/cipher/cipher-info.ts | 48 ++++ .../content/components/cipher/cipher-item.ts | 65 +++++ .../content/components/cipher/index.ts | 5 + .../content/components/cipher/types.ts | 44 +++ .../content/components/constants/styles.ts | 206 ++++++++++++++ .../content/components/dropdown-menu.ts | 121 ++++++++ .../content/components/icons/angle-down.ts | 27 ++ .../components/icons/brand-icon-container.ts | 19 ++ .../content/components/icons/business.ts | 46 +++ .../content/components/icons/close.ts | 27 ++ .../components/icons/exclamation-triangle.ts | 27 ++ .../content/components/icons/family.ts | 27 ++ .../content/components/icons/folder.ts | 27 ++ .../content/components/icons/globe.ts | 28 ++ .../content/components/icons/index.ts | 12 + .../content/components/icons/party-horn.ts | 174 ++++++++++++ .../content/components/icons/pencil-square.ts | 27 ++ .../content/components/icons/shield.ts | 19 ++ .../autofill/content/components/icons/user.ts | 27 ++ .../content/components/notification/body.ts | 69 +++++ .../components/notification/container.ts | 99 +++++++ .../content/components/notification/footer.ts | 42 +++ .../components/notification/header-message.ts | 25 ++ .../content/components/notification/header.ts | 61 ++++ .../content/components/rows/action-row.ts | 53 ++++ .../content/components/rows/button-row.ts | 73 +++++ .../content/components/rows/item-row.ts | 56 ++++ .../abstractions/notification-bar.ts | 13 +- package-lock.json | 265 +++++++++++++++--- package.json | 2 + 37 files changed, 2019 insertions(+), 46 deletions(-) create mode 100644 apps/browser/src/autofill/content/components/buttons/action-button.ts create mode 100644 apps/browser/src/autofill/content/components/buttons/badge-button.ts create mode 100644 apps/browser/src/autofill/content/components/buttons/close-button.ts create mode 100644 apps/browser/src/autofill/content/components/buttons/edit-button.ts create mode 100644 apps/browser/src/autofill/content/components/cipher/cipher-action.ts create mode 100644 apps/browser/src/autofill/content/components/cipher/cipher-icon.ts create mode 100644 apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts create mode 100644 apps/browser/src/autofill/content/components/cipher/cipher-info.ts create mode 100644 apps/browser/src/autofill/content/components/cipher/cipher-item.ts create mode 100644 apps/browser/src/autofill/content/components/cipher/index.ts create mode 100644 apps/browser/src/autofill/content/components/cipher/types.ts create mode 100644 apps/browser/src/autofill/content/components/constants/styles.ts create mode 100644 apps/browser/src/autofill/content/components/dropdown-menu.ts create mode 100644 apps/browser/src/autofill/content/components/icons/angle-down.ts create mode 100644 apps/browser/src/autofill/content/components/icons/brand-icon-container.ts create mode 100644 apps/browser/src/autofill/content/components/icons/business.ts create mode 100644 apps/browser/src/autofill/content/components/icons/close.ts create mode 100644 apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts create mode 100644 apps/browser/src/autofill/content/components/icons/family.ts create mode 100644 apps/browser/src/autofill/content/components/icons/folder.ts create mode 100644 apps/browser/src/autofill/content/components/icons/globe.ts create mode 100644 apps/browser/src/autofill/content/components/icons/index.ts create mode 100644 apps/browser/src/autofill/content/components/icons/party-horn.ts create mode 100644 apps/browser/src/autofill/content/components/icons/pencil-square.ts create mode 100644 apps/browser/src/autofill/content/components/icons/shield.ts create mode 100644 apps/browser/src/autofill/content/components/icons/user.ts create mode 100644 apps/browser/src/autofill/content/components/notification/body.ts create mode 100644 apps/browser/src/autofill/content/components/notification/container.ts create mode 100644 apps/browser/src/autofill/content/components/notification/footer.ts create mode 100644 apps/browser/src/autofill/content/components/notification/header-message.ts create mode 100644 apps/browser/src/autofill/content/components/notification/header.ts create mode 100644 apps/browser/src/autofill/content/components/rows/action-row.ts create mode 100644 apps/browser/src/autofill/content/components/rows/button-row.ts create mode 100644 apps/browser/src/autofill/content/components/rows/item-row.ts diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts new file mode 100644 index 00000000000..a9b4742b448 --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -0,0 +1,66 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { border, themes, typography, spacing } from "../constants/styles"; + +export function ActionButton({ + buttonAction, + buttonText, + disabled = false, + theme, +}: { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}) { + const handleButtonClick = (event: Event) => { + if (!disabled) { + buttonAction(event); + } + }; + + return html` + + `; +} + +const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css` + ${typography.body2} + + user-select: none; + border: 1px solid transparent; + border-radius: ${border.radius.full}; + padding: ${spacing["1"]} ${spacing["3"]}; + width: 100%; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + font-weight: 700; + + ${disabled + ? ` + background-color: ${themes[theme].secondary["300"]}; + color: ${themes[theme].text.muted}; + ` + : ` + background-color: ${themes[theme].primary["600"]}; + cursor: pointer; + color: ${themes[theme].text.contrast}; + + :hover { + border-color: ${themes[theme].primary["700"]}; + background-color: ${themes[theme].primary["700"]}; + color: ${themes[theme].text.contrast}; + } + `} +`; diff --git a/apps/browser/src/autofill/content/components/buttons/badge-button.ts b/apps/browser/src/autofill/content/components/buttons/badge-button.ts new file mode 100644 index 00000000000..3b3b84f8166 --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/badge-button.ts @@ -0,0 +1,67 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { border, themes, typography, spacing } from "../constants/styles"; + +export function BadgeButton({ + buttonAction, + buttonText, + disabled = false, + theme, +}: { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}) { + const handleButtonClick = (event: Event) => { + if (!disabled) { + buttonAction(event); + } + }; + + return html` + + `; +} + +const badgeButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css` + ${typography.helperMedium} + + user-select: none; + border-radius: ${border.radius.full}; + padding: ${spacing["1"]} ${spacing["2"]}; + max-height: fit-content; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + font-weight: 500; + + ${disabled + ? ` + border: 0.5px solid ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].secondary["300"]}; + color: ${themes[theme].text.muted}; + ` + : ` + border: 0.5px solid ${themes[theme].primary["700"]}; + background-color: ${themes[theme].primary["100"]}; + cursor: pointer; + color: ${themes[theme].primary["700"]}; + + :hover { + border-color: ${themes[theme].primary["600"]}; + background-color: ${themes[theme].primary["600"]}; + color: ${themes[theme].text.contrast}; + } + `} +`; diff --git a/apps/browser/src/autofill/content/components/buttons/close-button.ts b/apps/browser/src/autofill/content/components/buttons/close-button.ts new file mode 100644 index 00000000000..c32d0c130e3 --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/close-button.ts @@ -0,0 +1,39 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { spacing, themes } from "../constants/styles"; +import { Close as CloseIcon } from "../icons"; + +export function CloseButton({ + handleCloseNotification, + theme, +}: { + handleCloseNotification: (e: Event) => void; + theme: Theme; +}) { + return html` + + `; +} + +const closeButtonStyles = (theme: Theme) => css` + border: 1px solid transparent; + border-radius: ${spacing["1"]}; + background-color: transparent; + cursor: pointer; + width: 36px; + height: 36px; + + :hover { + border: 1px solid ${themes[theme].primary["600"]}; + } + + > svg { + width: 20px; + height: 20px; + } +`; diff --git a/apps/browser/src/autofill/content/components/buttons/edit-button.ts b/apps/browser/src/autofill/content/components/buttons/edit-button.ts new file mode 100644 index 00000000000..695cbfd3b9d --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts @@ -0,0 +1,60 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes, typography, spacing } from "../constants/styles"; +import { PencilSquare } from "../icons"; + +export function EditButton({ + buttonAction, + buttonText, + disabled = false, + theme, +}: { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}) { + return html` + + `; +} + +const editButtonStyles = ({ disabled, theme }: { disabled?: boolean; theme: Theme }) => css` + ${typography.helperMedium} + + user-select: none; + display: flex; + border: 1px solid transparent; + border-radius: ${spacing["1"]}; + background-color: transparent; + padding: ${spacing["1"]}; + max-height: fit-content; + overflow: hidden; + + ${!disabled + ? ` + cursor: pointer; + + :hover { + border-color: ${themes[theme].primary["600"]}; + } + ` + : ""} + + > svg { + width: 16px; + height: fit-content; + } +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts new file mode 100644 index 00000000000..2d386d34d6a --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts @@ -0,0 +1,31 @@ +import { Theme } from "@bitwarden/common/platform/enums"; + +import { BadgeButton } from "../../../content/components/buttons/badge-button"; +import { EditButton } from "../../../content/components/buttons/edit-button"; +import { NotificationTypes } from "../../../notification/abstractions/notification-bar"; + +export function CipherAction({ + handleAction = () => { + /* no-op */ + }, + notificationType, + theme, +}: { + handleAction?: (e: Event) => void; + notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; + theme: Theme; +}) { + return notificationType === NotificationTypes.Change + ? BadgeButton({ + buttonAction: handleAction, + // @TODO localize + buttonText: "Update item", + theme, + }) + : EditButton({ + buttonAction: handleAction, + // @TODO localize + buttonText: "Edit item", + theme, + }); +} diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-icon.ts b/apps/browser/src/autofill/content/components/cipher/cipher-icon.ts new file mode 100644 index 00000000000..73d3f7604a9 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-icon.ts @@ -0,0 +1,33 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { Globe } from "../../../content/components/icons"; + +/** + * @param {string} props.color contextual color override if no icon URI is available + * @param {string} props.size valid CSS `width` value, represents the width-basis of the graphic, with height maintaining original aspect-ratio + */ +export function CipherIcon({ + color, + size, + theme, + uri, +}: { + color: string; + size: string; + theme: Theme; + uri?: string; +}) { + const iconClass = cipherIconStyle({ width: size }); + + return uri + ? html`` + : html`${Globe({ color, theme })}`; +} + +const cipherIconStyle = ({ width }: { width: string }) => css` + width: ${width}; + height: fit-content; +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts b/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts new file mode 100644 index 00000000000..38b4292f8e5 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-indicator-icons.ts @@ -0,0 +1,35 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes } from "../../../content/components/constants/styles"; +import { Business, Family } from "../../../content/components/icons"; + +// @TODO connect data source to icon checks +// @TODO support other indicator types (attachments, etc) +export function CipherInfoIndicatorIcons({ + isBusinessOrg, + isFamilyOrg, + theme, +}: { + isBusinessOrg?: boolean; + isFamilyOrg?: boolean; + theme: Theme; +}) { + const indicatorIcons = [ + ...(isBusinessOrg ? [Business({ color: themes[theme].text.muted, theme })] : []), + ...(isFamilyOrg ? [Family({ color: themes[theme].text.muted, theme })] : []), + ]; + + return indicatorIcons.length + ? html` ${indicatorIcons} ` + : null; // @TODO null case should be handled by parent +} + +const cipherInfoIndicatorIconsStyles = css` + > svg { + width: fit-content; + height: 12px; + } +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-info.ts b/apps/browser/src/autofill/content/components/cipher/cipher-info.ts new file mode 100644 index 00000000000..de374b44a97 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-info.ts @@ -0,0 +1,48 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes, typography } from "../../../content/components/constants/styles"; + +import { CipherInfoIndicatorIcons } from "./cipher-indicator-icons"; +import { CipherData } from "./types"; + +// @TODO support other cipher types (card, identity, notes, etc) +export function CipherInfo({ cipher, theme }: { cipher: CipherData; theme: Theme }) { + const { name, login } = cipher; + + return html` +
    + + ${[name, CipherInfoIndicatorIcons({ theme })]} + + + ${login?.username + ? html`${login.username}` + : null} +
    + `; +} + +const cipherInfoPrimaryTextStyles = (theme: Theme) => css` + ${typography.body2} + + gap: 2px; + display: flex; + display: block; + overflow-x: hidden; + text-overflow: ellipsis; + color: ${themes[theme].text.main}; + font-weight: 500; +`; + +const cipherInfoSecondaryTextStyles = (theme: Theme) => css` + ${typography.helperMedium} + + display: block; + overflow-x: hidden; + text-overflow: ellipsis; + color: ${themes[theme].text.muted}; + font-weight: 400; +`; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts new file mode 100644 index 00000000000..651c20cac3a --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts @@ -0,0 +1,65 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { spacing, themes } from "../../../content/components/constants/styles"; +import { + NotificationType, + NotificationTypes, +} from "../../../notification/abstractions/notification-bar"; + +import { CipherAction } from "./cipher-action"; +import { CipherIcon } from "./cipher-icon"; +import { CipherInfo } from "./cipher-info"; +import { CipherData } from "./types"; + +const cipherIconWidth = "24px"; + +export function CipherItem({ + cipher, + handleAction, + notificationType, + theme = ThemeTypes.Light, +}: { + cipher: CipherData; + handleAction?: (e: Event) => void; + notificationType?: NotificationType; + theme: Theme; +}) { + const { icon } = cipher; + const uri = (icon.imageEnabled && icon.image) || undefined; + + let cipherActionButton = null; + + if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { + cipherActionButton = html`
    + ${CipherAction({ handleAction, notificationType, theme })} +
    `; + } + + return html` +
    + ${CipherIcon({ color: themes[theme].text.muted, size: cipherIconWidth, theme, uri })} + ${CipherInfo({ theme, cipher })} +
    + ${cipherActionButton} + `; +} + +const cipherItemStyles = css` + gap: ${spacing["2"]}; + display: flex; + flex-wrap: nowrap; + align-items: center; + justify-content: start; + + > img, + > span { + display: flex; + } + + > div { + max-width: calc(100% - ${cipherIconWidth} - ${spacing["2"]}); + } +`; diff --git a/apps/browser/src/autofill/content/components/cipher/index.ts b/apps/browser/src/autofill/content/components/cipher/index.ts new file mode 100644 index 00000000000..733ddb74b4d --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/index.ts @@ -0,0 +1,5 @@ +export * from "./cipher-action"; +export * from "./cipher-icon"; +export * from "./cipher-indicator-icons"; +export * from "./cipher-info"; +export * from "./cipher-item"; diff --git a/apps/browser/src/autofill/content/components/cipher/types.ts b/apps/browser/src/autofill/content/components/cipher/types.ts new file mode 100644 index 00000000000..24f528c5246 --- /dev/null +++ b/apps/browser/src/autofill/content/components/cipher/types.ts @@ -0,0 +1,44 @@ +const CipherTypes = { + Login: 1, + SecureNote: 2, + Card: 3, + Identity: 4, +} as const; + +type CipherType = (typeof CipherTypes)[keyof typeof CipherTypes]; + +const CipherRepromptTypes = { + None: 0, + Password: 1, +} as const; + +type CipherRepromptType = (typeof CipherRepromptTypes)[keyof typeof CipherRepromptTypes]; + +export type WebsiteIconData = { + imageEnabled: boolean; + image: string; + fallbackImage: string; + icon: string; +}; + +export type CipherData = { + id: string; + name: string; + type: CipherType; + reprompt: CipherRepromptType; + favorite: boolean; + icon: WebsiteIconData; + accountCreationFieldType?: string; + login?: { + username: string; + passkey: { + rpName: string; + userName: string; + } | null; + }; + card?: string; + identity?: { + fullName: string; + username?: string; + }; +}; diff --git a/apps/browser/src/autofill/content/components/constants/styles.ts b/apps/browser/src/autofill/content/components/constants/styles.ts new file mode 100644 index 00000000000..cd6054e90ba --- /dev/null +++ b/apps/browser/src/autofill/content/components/constants/styles.ts @@ -0,0 +1,206 @@ +import { Theme } from "@bitwarden/common/platform/enums"; + +const lightTheme = { + transparent: { + hover: `rgb(0 0 0 / 0.02)`, + }, + shadow: `rgba(168 179 200)`, + primary: { + 100: `rgba(219, 229, 246)`, + 300: `rgba(121, 161, 233)`, + 600: `rgba(23, 93, 220)`, + 700: `rgba(26, 65, 172)`, + }, + secondary: { + 100: `rgba(230, 233, 239)`, + 300: `rgba(168, 179, 200)`, + 500: `rgba(90, 109, 145)`, + 600: `rgba(83, 99, 131)`, + 700: `rgba(63, 75, 99)`, + }, + success: { + 100: `rgba(219, 229, 246)`, + 600: `rgba(121, 161, 233)`, + 700: `rgba(26, 65, 172)`, + }, + danger: { + 100: `rgba(255, 236, 239)`, + 600: `rgba(203, 38, 58)`, + 700: `rgba(149, 27, 42)`, + }, + warning: { + 100: `rgba(255, 248, 228)`, + 600: `rgba(255, 191, 0)`, + 700: `rgba(172, 88, 0)`, + }, + info: { + 100: `rgba(219, 229, 246)`, + 600: `rgba(121, 161, 233)`, + 700: `rgba(26, 65, 172)`, + }, + art: { + primary: `rgba(2, 15, 102)`, + accent: `rgba(44, 221, 223)`, + }, + text: { + main: `rgba(27, 32, 41)`, + muted: `rgba(90, 109, 145)`, + contrast: `rgba(255, 255, 255)`, + alt2: `rgba(255, 255, 255)`, + code: `rgba(192, 17, 118)`, + }, + background: { + DEFAULT: `rgba(255, 255, 255)`, + alt: `rgba(243, 246, 249)`, + alt2: `rgba(23, 92, 219)`, + alt3: `rgba(26, 65, 172)`, + alt4: `rgba(2, 15, 102)`, + }, + brandLogo: `rgba(23, 93, 220)`, +}; + +const darkTheme = { + transparent: { + hover: `rgb(255 255 255 / 0.02)`, + }, + shadow: `rgba(0, 0, 0)`, + primary: { + 100: `rgba(26, 39, 78)`, + 300: `rgba(26, 65, 172)`, + 600: `rgba(101, 171, 255)`, + 700: `rgba(170, 195, 239)`, + }, + secondary: { + 100: `rgba(48, 57, 70)`, + 300: `rgba(82, 91, 106)`, + 500: `rgba(121, 128, 142)`, + 600: `rgba(143, 152, 166)`, + 700: `rgba(158, 167, 181)`, + }, + success: { + 100: `rgba(11, 111, 21)`, + 600: `rgba(107, 241, 120)`, + 700: `rgba(191, 236, 195)`, + }, + danger: { + 100: `rgba(149, 27, 42)`, + 600: `rgba(255, 78, 99)`, + 700: `rgba(255, 236, 239)`, + }, + warning: { + 100: `rgba(172, 88, 0)`, + 600: `rgba(255, 191, 0)`, + 700: `rgba(255, 248, 228)`, + }, + info: { + 100: `rgba(26, 65, 172)`, + 600: `rgba(121, 161, 233)`, + 700: `rgba(219, 229, 246)`, + }, + art: { + primary: `rgba(243, 246, 249)`, + accent: `rgba(44, 221, 233)`, + }, + text: { + main: `rgba(243, 246, 249)`, + muted: `rgba(136, 152, 181)`, + contrast: `rgba(18 26 39)`, + alt2: `rgba(255, 255, 255)`, + code: `rgba(255, 143, 208)`, + }, + background: { + DEFAULT: `rgba(32, 39, 51)`, + alt: `rgba(18, 26, 39)`, + alt2: `rgba(47, 52, 61)`, + alt3: `rgba(48, 57, 70)`, + alt4: `rgba(18, 26, 39)`, + }, + brandLogo: `rgba(255, 255, 255)`, +}; + +export const themes = { + light: lightTheme, + dark: darkTheme, + + // For compatibility + system: lightTheme, + nord: lightTheme, + solarizedDark: darkTheme, +}; + +export const spacing = { + 4: `16px`, + 3: `12px`, + 2: `8px`, + "1.5": `6px`, + 1: `4px`, +}; + +export const border = { + radius: { + large: `8px`, + full: `9999px`, + }, +}; + +export const typography = { + body1: ` + line-height: 24px; + font-family: "DM Sans", sans-serif; + font-size: 16px; + `, + body2: ` + line-height: 20px; + font-family: "DM Sans", sans-serif; + font-size: 14px; + `, + helperMedium: ` + line-height: 16px; + font-family: "DM Sans", sans-serif; + font-size: 12px; + `, +}; + +export const ruleNames = { + fill: "fill", + stroke: "stroke", +} as const; + +type RuleName = (typeof ruleNames)[keyof typeof ruleNames]; + +/* + * `color` is an intentionally generic name here, since either fill or stroke may apply, due to + * inconsistent SVG construction. This consequently precludes dynamic multi-colored icons here. + */ +export const buildIconColorRule = (color: string, rule: RuleName = ruleNames.fill) => ` + ${rule}: ${color}; +`; + +export function scrollbarStyles(theme: Theme) { + return { + default: ` + /* FireFox & Chrome support */ + scrollbar-color: ${themes[theme].secondary["500"]} ${themes[theme].background.alt}; + `, + safari: ` + /* Safari Support */ + ::-webkit-scrollbar { + overflow: auto; + } + ::-webkit-scrollbar-thumb { + border-width: 4px; + border-style: solid; + border-radius: 0.5rem; + border-color: transparent; + background-clip: content-box; + background-color: ${themes[theme].secondary["500"]}; + } + ::-webkit-scrollbar-track { + ${themes[theme].background.alt}; + } + ::-webkit-scrollbar-thumb:hover { + ${themes[theme].secondary["600"]}; + } + `, + }; +} diff --git a/apps/browser/src/autofill/content/components/dropdown-menu.ts b/apps/browser/src/autofill/content/components/dropdown-menu.ts new file mode 100644 index 00000000000..3e3874b37d7 --- /dev/null +++ b/apps/browser/src/autofill/content/components/dropdown-menu.ts @@ -0,0 +1,121 @@ +import { css } from "@emotion/css"; +import { html, TemplateResult } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { border, themes, typography, spacing } from "./constants/styles"; +import { AngleDown } from "./icons"; + +export function DropdownMenu({ + buttonText, + icon, + disabled = false, + selectAction, + theme, +}: { + selectAction?: (e: Event) => void; + buttonText: string; + icon?: TemplateResult; + disabled?: boolean; + theme: Theme; +}) { + // @TODO placeholder/will not work; make stateful + const showDropdown = false; + const handleButtonClick = (event: Event) => { + // if (!disabled) { + // // show dropdown + // showDropdown = !showDropdown; + // this.requestUpdate(); + // } + }; + + const dropdownMenuItems: TemplateResult[] = []; + + return html` +
    + + ${showDropdown + ? html`
    ${dropdownMenuItems}
    ` + : null} +
    + `; +} + +const iconSize = "15px"; + +const dropdownContainerStyles = css` + display: flex; + + > div, + > button { + width: 100%; + } +`; + +const dropdownButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css` + ${typography.body2} + + font-weight: 400; + gap: ${spacing["1.5"]}; + user-select: none; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + border-radius: ${border.radius.full}; + padding: ${spacing["1"]} ${spacing["2"]}; + max-height: fit-content; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + + > svg { + max-width: ${iconSize}; + height: fit-content; + } + + ${disabled + ? ` + border: 1px solid ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].secondary["300"]}; + color: ${themes[theme].text.muted}; + ` + : ` + border: 1px solid ${themes[theme].text.muted}; + background-color: transparent; + cursor: pointer; + color: ${themes[theme].text.muted}; + + :hover { + border-color: ${themes[theme].secondary["700"]}; + background-color: ${themes[theme].secondary["100"]}; + } + `} +`; + +const dropdownButtonTextStyles = css` + max-width: calc(100% - ${iconSize} - ${iconSize}); + overflow-x: hidden; + text-overflow: ellipsis; +`; + +const dropdownMenuStyles = ({ theme }: { theme: Theme }) => css` + color: ${themes[theme].text.main}; + border: 1px solid ${themes[theme].secondary["500"]}; + border-radius: 0.5rem; + background-clip: padding-box; + background-color: ${themes[theme].background.DEFAULT}; + padding: 0.25rem 0.75rem; + position: absolute; + overflow-y: auto; +`; diff --git a/apps/browser/src/autofill/content/components/icons/angle-down.ts b/apps/browser/src/autofill/content/components/icons/angle-down.ts new file mode 100644 index 00000000000..4b85319c18a --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/angle-down.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function AngleDown({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/brand-icon-container.ts b/apps/browser/src/autofill/content/components/icons/brand-icon-container.ts new file mode 100644 index 00000000000..8df68d79b6e --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/brand-icon-container.ts @@ -0,0 +1,19 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { Shield } from "./shield"; + +export function BrandIconContainer({ iconLink, theme }: { iconLink?: URL; theme: Theme }) { + const Icon = html`
    ${Shield({ theme })}
    `; + + return iconLink ? html`${Icon}` : Icon; +} + +const brandIconContainerStyles = css` + > svg { + width: 20px; + height: fit-content; + } +`; diff --git a/apps/browser/src/autofill/content/components/icons/business.ts b/apps/browser/src/autofill/content/components/icons/business.ts new file mode 100644 index 00000000000..547ee82b547 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/business.ts @@ -0,0 +1,46 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Business({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/close.ts b/apps/browser/src/autofill/content/components/icons/close.ts new file mode 100644 index 00000000000..c94a4b20a6a --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/close.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Close({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts b/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts new file mode 100644 index 00000000000..bcc7b3d5432 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/exclamation-triangle.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function ExclamationTriangle({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/family.ts b/apps/browser/src/autofill/content/components/icons/family.ts new file mode 100644 index 00000000000..33e2e422ced --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/family.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Family({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/folder.ts b/apps/browser/src/autofill/content/components/icons/folder.ts new file mode 100644 index 00000000000..7e1f8f197f6 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/folder.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Folder({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/globe.ts b/apps/browser/src/autofill/content/components/icons/globe.ts new file mode 100644 index 00000000000..6697fa93b70 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/globe.ts @@ -0,0 +1,28 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Globe({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/index.ts b/apps/browser/src/autofill/content/components/icons/index.ts new file mode 100644 index 00000000000..992b034afa7 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/index.ts @@ -0,0 +1,12 @@ +export { AngleDown } from "./angle-down"; +export { BrandIconContainer } from "./brand-icon-container"; +export { Business } from "./business"; +export { Close } from "./close"; +export { ExclamationTriangle } from "./exclamation-triangle"; +export { Family } from "./family"; +export { Folder } from "./folder"; +export { Globe } from "./globe"; +export { PartyHorn } from "./party-horn"; +export { PencilSquare } from "./pencil-square"; +export { Shield } from "./shield"; +export { User } from "./user"; diff --git a/apps/browser/src/autofill/content/components/icons/party-horn.ts b/apps/browser/src/autofill/content/components/icons/party-horn.ts new file mode 100644 index 00000000000..dc2144b524f --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/party-horn.ts @@ -0,0 +1,174 @@ +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +// This icon has static multi-colors for each theme +export function PartyHorn({ theme }: { theme: Theme }) { + if (theme === ThemeTypes.Dark) { + return html` + + + + + + + + + + + + + + `; + } + + return html` + + + + + + + + + + + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/pencil-square.ts b/apps/browser/src/autofill/content/components/icons/pencil-square.ts new file mode 100644 index 00000000000..45a8429f883 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/pencil-square.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function PencilSquare({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/shield.ts b/apps/browser/src/autofill/content/components/icons/shield.ts new file mode 100644 index 00000000000..5ffd953e869 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/shield.ts @@ -0,0 +1,19 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function Shield({ color, theme }: { color?: string; theme: Theme }) { + const shapeColor = color || themes[theme].brandLogo; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/icons/user.ts b/apps/browser/src/autofill/content/components/icons/user.ts new file mode 100644 index 00000000000..6babcfa39a9 --- /dev/null +++ b/apps/browser/src/autofill/content/components/icons/user.ts @@ -0,0 +1,27 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; + +export function User({ + color, + disabled, + theme, +}: { + color?: string; + disabled?: boolean; + theme: Theme; +}) { + const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; + + return html` + + + + `; +} diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts new file mode 100644 index 00000000000..6a3ed2e5d1e --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -0,0 +1,69 @@ +import createEmotion from "@emotion/css/create-instance"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { NotificationType } from "../../../notification/abstractions/notification-bar"; +import { CipherItem } from "../cipher"; +import { CipherData } from "../cipher/types"; +import { scrollbarStyles, spacing, themes, typography } from "../constants/styles"; +import { ItemRow } from "../rows/item-row"; + +export const componentClassPrefix = "notification-body"; + +const { css } = createEmotion({ + key: componentClassPrefix, +}); + +export function NotificationBody({ + ciphers, + notificationType, + theme = ThemeTypes.Light, +}: { + ciphers: CipherData[]; + customClasses?: string[]; + notificationType?: NotificationType; + theme: Theme; +}) { + // @TODO get client vendor from context + const isSafari = false; + + return html` +
    + ${ciphers.map((cipher) => + ItemRow({ + theme, + children: CipherItem({ + cipher, + notificationType, + theme, + handleAction: () => { + // @TODO connect update or edit actions to handler + }, + }), + }), + )} +
    + `; +} + +const notificationBodyStyles = ({ isSafari, theme }: { isSafari: boolean; theme: Theme }) => css` + ${typography.body1} + + gap: ${spacing["1.5"]}; + display: flex; + flex-flow: column; + background-color: ${themes[theme].background.alt}; + max-height: 123px; + overflow-x: hidden; + overflow-y: auto; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-weight: 400; + + :last-child { + border-radius: 0 0 ${spacing["4"]} ${spacing["4"]}; + } + + ${isSafari ? scrollbarStyles(theme).safari : scrollbarStyles(theme).default} +`; diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts new file mode 100644 index 00000000000..0cce066cf3a --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -0,0 +1,99 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { + NotificationBarIframeInitData, + NotificationTypes, + NotificationType, +} from "../../../notification/abstractions/notification-bar"; +import { createAutofillOverlayCipherDataMock } from "../../../spec/autofill-mocks"; +import { CipherData } from "../cipher/types"; +import { themes, spacing } from "../constants/styles"; + +import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body"; +import { NotificationFooter } from "./footer"; +import { + NotificationHeader, + componentClassPrefix as notificationHeaderClassPrefix, +} from "./header"; + +export function NotificationContainer({ + handleCloseNotification, + i18n, + theme = ThemeTypes.Light, + type, +}: NotificationBarIframeInitData & { handleCloseNotification: (e: Event) => void } & { + i18n: { [key: string]: string }; + type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` +}) { + const headerMessage = getHeaderMessage(i18n, type); + const showBody = true; + + // @TODO remove mock ciphers for development + const ciphers = [ + createAutofillOverlayCipherDataMock(1), + { ...createAutofillOverlayCipherDataMock(2), icon: { imageEnabled: false } }, + { + ...createAutofillOverlayCipherDataMock(3), + icon: { imageEnabled: true, image: "https://localhost:8443/icons/webtests.dev/icon.png" }, + }, + ] as CipherData[]; + + return html` +
    + ${NotificationHeader({ + handleCloseNotification, + standalone: showBody, + message: headerMessage, + theme, + })} + ${showBody + ? NotificationBody({ + ciphers, + notificationType: type, + theme, + }) + : null} + ${NotificationFooter({ + theme, + notificationType: type, + })} +
    + `; +} + +const notificationContainerStyles = (theme: Theme) => css` + position: absolute; + right: 20px; + border: 1px solid ${themes[theme].secondary["300"]}; + border-radius: ${spacing["4"]}; + box-shadow: -2px 4px 6px 0px #0000001a; + background-color: ${themes[theme].background.alt}; + width: 400px; + + [class*="${notificationHeaderClassPrefix}-"] { + border-radius: ${spacing["4"]} ${spacing["4"]} 0 0; + } + + [class*="${notificationBodyClassPrefix}-"] { + margin: ${spacing["3"]} 0 ${spacing["1.5"]} ${spacing["3"]}; + padding-right: ${spacing["3"]}; + } +`; + +function getHeaderMessage(i18n: { [key: string]: string }, type?: NotificationType) { + switch (type) { + case NotificationTypes.Add: + return i18n.saveAsNewLoginAction; + case NotificationTypes.Change: + return i18n.updateLoginPrompt; + case NotificationTypes.Unlock: + return ""; + case NotificationTypes.FilelessImport: + return ""; + default: + return undefined; + } +} diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts new file mode 100644 index 00000000000..91a72dc9aab --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -0,0 +1,42 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { + NotificationType, + NotificationTypes, +} from "../../../notification/abstractions/notification-bar"; +import { spacing, themes } from "../constants/styles"; +import { ActionRow } from "../rows/action-row"; +import { ButtonRow } from "../rows/button-row"; + +export function NotificationFooter({ + notificationType, + theme, +}: { + notificationType?: NotificationType; + theme: Theme; +}) { + const isChangeNotification = notificationType === NotificationTypes.Change; + // @TODO localize + const saveNewItemText = "Save as new login"; + + return html` +
    + ${isChangeNotification + ? ActionRow({ itemText: saveNewItemText, handleAction: () => {}, theme }) + : ButtonRow({ theme })} +
    + `; +} + +const notificationFooterStyles = ({ theme }: { theme: Theme }) => css` + display: flex; + background-color: ${themes[theme].background.alt}; + padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]}; + + :last-child { + border-radius: 0 0 ${spacing["4"]} ${spacing["4"]}; + } +`; diff --git a/apps/browser/src/autofill/content/components/notification/header-message.ts b/apps/browser/src/autofill/content/components/notification/header-message.ts new file mode 100644 index 00000000000..ccfa58b8970 --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/header-message.ts @@ -0,0 +1,25 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { themes } from "../constants/styles"; + +export function NotificationHeaderMessage({ message, theme }: { message: string; theme: Theme }) { + return html` + ${message} + `; +} + +const notificationHeaderMessageStyles = (theme: Theme) => css` + flex-grow: 1; + overflow-x: hidden; + text-align: left; + text-overflow: ellipsis; + line-height: 28px; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-family: "DM Sans", sans-serif; + font-size: 18px; + font-weight: 600; +`; diff --git a/apps/browser/src/autofill/content/components/notification/header.ts b/apps/browser/src/autofill/content/components/notification/header.ts new file mode 100644 index 00000000000..85f6e48cd5d --- /dev/null +++ b/apps/browser/src/autofill/content/components/notification/header.ts @@ -0,0 +1,61 @@ +import createEmotion from "@emotion/css/create-instance"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { CloseButton } from "../buttons/close-button"; +import { themes } from "../constants/styles"; +import { BrandIconContainer } from "../icons/brand-icon-container"; + +import { NotificationHeaderMessage } from "./header-message"; + +export const componentClassPrefix = "notification-header"; + +const { css } = createEmotion({ + key: componentClassPrefix, +}); + +export function NotificationHeader({ + message, + standalone, + theme = ThemeTypes.Light, + handleCloseNotification, +}: { + message?: string; + standalone: boolean; + theme: Theme; + handleCloseNotification: (e: Event) => void; +}) { + const showIcon = true; + const isDismissable = true; + + return html` +
    + ${showIcon ? BrandIconContainer({ theme }) : null} + ${message ? NotificationHeaderMessage({ message, theme }) : null} + ${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null} +
    + `; +} + +const notificationHeaderStyles = ({ + standalone, + theme, +}: { + standalone: boolean; + theme: Theme; +}) => css` + gap: 8px; + display: flex; + align-items: center; + justify-content: flex-start; + background-color: ${themes[theme].background.alt}; + padding: 12px 16px 8px 16px; + white-space: nowrap; + + ${standalone + ? css` + border-bottom: 0.5px solid ${themes[theme].secondary["300"]}; + ` + : css``} +`; diff --git a/apps/browser/src/autofill/content/components/rows/action-row.ts b/apps/browser/src/autofill/content/components/rows/action-row.ts new file mode 100644 index 00000000000..ad58411baf4 --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/action-row.ts @@ -0,0 +1,53 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { spacing, themes, typography } from "../../../content/components/constants/styles"; + +export function ActionRow({ + handleAction, + itemText, + theme = ThemeTypes.Light, +}: { + itemText: string; + handleAction?: (e: Event) => void; + theme: Theme; +}) { + return html` + + `; +} + +const actionRowStyles = (theme: Theme) => css` + ${typography.body2} + + user-select: none; + border-width: 0 0 0.5px 0; + border-style: solid; + border-radius: ${spacing["2"]}; + border-color: ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].background.DEFAULT}; + cursor: pointer; + padding: ${spacing["2"]} ${spacing["3"]}; + width: 100%; + min-height: 40px; + text-align: left; + color: ${themes[theme].primary["600"]}; + font-weight: 700; + + > span { + display: block; + width: calc(100% - 5px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + :hover { + background-color: ${themes[theme].primary["100"]}; + color: ${themes[theme].primary["600"]}; + } +`; diff --git a/apps/browser/src/autofill/content/components/rows/button-row.ts b/apps/browser/src/autofill/content/components/rows/button-row.ts new file mode 100644 index 00000000000..ce14a242e97 --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/button-row.ts @@ -0,0 +1,73 @@ +import { css } from "@emotion/css"; +import { html, TemplateResult } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { ActionButton } from "../../../content/components/buttons/action-button"; +import { spacing, themes } from "../../../content/components/constants/styles"; +import { Folder, User } from "../../../content/components/icons"; +import { DropdownMenu } from "../dropdown-menu"; + +export function ButtonRow({ theme }: { theme: Theme }) { + return html` +
    + ${[ + ActionButton({ + buttonAction: () => {}, + buttonText: "Action Button", + theme, + }), + DropdownContainer({ + children: [ + DropdownMenu({ + buttonText: "You", + icon: User({ color: themes[theme].text.muted, theme }), + theme, + }), + DropdownMenu({ + buttonText: "Folder", + icon: Folder({ color: themes[theme].text.muted, theme }), + disabled: true, + theme, + }), + ], + }), + ]} +
    + `; +} + +function DropdownContainer({ children }: { children: TemplateResult[] }) { + return html`
    ${children}
    `; +} + +const buttonRowStyles = css` + gap: 16px; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-height: 52px; + white-space: nowrap; + + > button { + max-width: min-content; + flex: 1 1 50%; + } + + > div { + flex: 1 1 min-content; + } +`; + +const dropdownContainerStyles = css` + gap: 8px; + display: flex; + align-items: center; + justify-content: flex-end; + overflow: hidden; + + > div { + min-width: calc(50% - ${spacing["1.5"]}); + } +`; diff --git a/apps/browser/src/autofill/content/components/rows/item-row.ts b/apps/browser/src/autofill/content/components/rows/item-row.ts new file mode 100644 index 00000000000..da00fd276ab --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/item-row.ts @@ -0,0 +1,56 @@ +import { css } from "@emotion/css"; +import { html, TemplateResult } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; + +import { spacing, themes, typography } from "../../../content/components/constants/styles"; + +export function ItemRow({ + theme = ThemeTypes.Light, + children, +}: { + theme: Theme; + children: TemplateResult | TemplateResult[]; +}) { + return html`
    ${children}
    `; +} + +export const itemRowStyles = ({ theme }: { theme: Theme }) => css` + ${typography.body1} + + gap: ${spacing["2"]}; + display: flex; + align-items: center; + justify-content: space-between; + border-width: 0 0 0.5px 0; + border-style: solid; + border-radius: ${spacing["2"]}; + border-color: ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].background.DEFAULT}; + padding: ${spacing["2"]} ${spacing["3"]}; + min-height: min-content; + max-height: 52px; + overflow-x: hidden; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-weight: 400; + + > div { + :first-child { + flex: 3 3 75%; + min-width: 25%; + } + + :not(:first-child) { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-end; + max-width: 25%; + + > button { + max-width: min-content; + } + } + } +`; diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index 425d53783e1..2e38adacb32 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -1,7 +1,16 @@ import { Theme } from "@bitwarden/common/platform/enums"; +const NotificationTypes = { + Add: "add", + Change: "change", + Unlock: "unlock", + FilelessImport: "fileless-import", +} as const; + +type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]; + type NotificationBarIframeInitData = { - type?: string; + type?: string; // @TODO use `NotificationType` isVaultLocked?: boolean; theme?: Theme; removeIndividualVault?: boolean; @@ -24,6 +33,8 @@ type NotificationBarWindowMessageHandlers = { }; export { + NotificationTypes, + NotificationType, NotificationBarIframeInitData, NotificationBarWindowMessage, NotificationBarWindowMessageHandlers, diff --git a/package-lock.json b/package-lock.json index c60d71881a5..7994c8b0c2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@angular/router": "18.2.13", "@bitwarden/sdk-internal": "0.2.0-main.38", "@electron/fuses": "1.8.0", + "@emotion/css": "11.13.5", "@koa/multer": "3.0.2", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", @@ -50,6 +51,7 @@ "koa": "2.15.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", + "lit": "3.2.1", "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.1", @@ -4344,7 +4346,6 @@ "version": "7.25.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -5650,6 +5651,109 @@ "node": "*" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", @@ -5836,6 +5940,50 @@ "lit": "^2.1.3" } }, + "node_modules/@figspec/components/node_modules/@lit/reactive-element": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", + "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.0.0" + } + }, + "node_modules/@figspec/components/node_modules/lit": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", + "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^1.6.0", + "lit-element": "^3.3.0", + "lit-html": "^2.8.0" + } + }, + "node_modules/@figspec/components/node_modules/lit-element": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", + "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.1.0", + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.8.0" + } + }, + "node_modules/@figspec/components/node_modules/lit-html": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", + "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/@figspec/react": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@figspec/react/-/react-1.0.3.tgz", @@ -6971,17 +7119,15 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@lit/reactive-element": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", - "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", - "dev": true, + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.0.0" + "@lit-labs/ssr-dom-shim": "^1.2.0" } }, "node_modules/@lmdb/lmdb-darwin-arm64": { @@ -9702,7 +9848,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "dev": true, "license": "MIT" }, "node_modules/@types/plist": { @@ -9871,7 +10016,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, "license": "MIT" }, "node_modules/@types/unist": { @@ -11961,6 +12105,46 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", @@ -12909,7 +13093,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -13812,7 +13995,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -14333,7 +14515,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/data-urls": { @@ -15560,7 +15741,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -15809,7 +15989,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -17241,6 +17420,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -18899,7 +19084,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -18916,7 +19100,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -19216,7 +19399,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, "license": "MIT" }, "node_modules/is-bigint": { @@ -19292,7 +19474,6 @@ "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -22116,7 +22297,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, "license": "MIT" }, "node_modules/lint-staged": { @@ -22459,34 +22639,31 @@ } }, "node_modules/lit": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", - "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", - "dev": true, + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz", + "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==", "license": "BSD-3-Clause", "dependencies": { - "@lit/reactive-element": "^1.6.0", - "lit-element": "^3.3.0", - "lit-html": "^2.8.0" + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.1.0", + "lit-html": "^3.2.0" } }, "node_modules/lit-element": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", - "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz", + "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==", "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.0", - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.8.0" + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.2.0" } }, "node_modules/lit-html": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", - "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", - "dev": true, + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz", + "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==", "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" @@ -26075,7 +26252,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -26088,7 +26264,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -26107,7 +26282,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/parse-node-version": { @@ -26371,7 +26545,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -26408,7 +26581,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -27759,7 +27931,6 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, "license": "MIT" }, "node_modules/regenerator-transform": { @@ -28058,7 +28229,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -29954,6 +30124,12 @@ "webpack": "^5.27.0" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -30016,7 +30192,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index 9922b6d7bac..0a78d370d26 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "@angular/router": "18.2.13", "@bitwarden/sdk-internal": "0.2.0-main.38", "@electron/fuses": "1.8.0", + "@emotion/css": "11.13.5", "@koa/multer": "3.0.2", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", @@ -180,6 +181,7 @@ "koa": "2.15.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", + "lit": "3.2.1", "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.1", From 5cb31f37e922864a82f2c9d1f1b72f4f62a6d34a Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Tue, 7 Jan 2025 15:10:42 -0500 Subject: [PATCH 084/270] [PM-16824] update new device verification notice page one so learn more link opens in browser from desktop (#12731) --- .../new-device-verification-notice-page-one.component.html | 7 +------ .../new-device-verification-notice-page-one.component.ts | 6 ++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html b/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html index ddff560fd00..9d7808379d3 100644 --- a/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html +++ b/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.html @@ -1,12 +1,7 @@

    {{ "newDeviceVerificationNoticeContentPage1" | i18n }} - + {{ "learnMore" | i18n }}.

    diff --git a/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.ts b/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.ts index 8127c368046..8db923fec88 100644 --- a/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.ts +++ b/libs/vault/src/components/new-device-verification-notice/new-device-verification-notice-page-one.component.ts @@ -121,4 +121,10 @@ export class NewDeviceVerificationNoticePageOneComponent implements OnInit, Afte await this.router.navigate(["/vault"]); }; + + navigateToNewDeviceVerificationHelp(event: Event) { + event.preventDefault(); + + this.platformUtilsService.launchUri("https://bitwarden.com/help/new-device-verification/"); + } } From d422e8531077c218715f554950ce290ee46fdcb4 Mon Sep 17 00:00:00 2001 From: Jimmy Vo Date: Tue, 7 Jan 2025 15:46:03 -0500 Subject: [PATCH 085/270] [14415] Extend VS Code extensions. (#12604) Add extensions that we believe will be useful for working in this repository to the Visual Studio recommended extensions list to make them more discoverable. --- clients.code-workspace | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clients.code-workspace b/clients.code-workspace index 1b956c25cee..f7d86d2a242 100644 --- a/clients.code-workspace +++ b/clients.code-workspace @@ -73,6 +73,15 @@ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "Angular.ng-template", + "nick-rudenko.back-n-forth", + "streetsidesoftware.code-spell-checker", + "MS-vsliveshare.vsliveshare", + "mhutchie.git-graph", + "donjayamanne.githistory", + "eamodio.gitlens", + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "vadimcn.vscode-lldb", ], }, } From 0bd988dac88d26667e04eea6e79067adb3a64e04 Mon Sep 17 00:00:00 2001 From: Evan Bassler Date: Tue, 7 Jan 2025 17:07:30 -0600 Subject: [PATCH 086/270] [PM-15190] hide empty ciphers from autofill (#12491) * hide empty ciphers from autofill --------- Co-authored-by: Evan Bassler --- .../background/overlay.background.spec.ts | 12 +++++++++- .../autofill/background/overlay.background.ts | 23 +++++++++++++++++++ apps/browser/src/autofill/utils/index.ts | 17 ++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 512a9ff4c2a..0ac69317855 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -1923,7 +1923,17 @@ describe("OverlayBackground", () => { it("returns true if the overlay login ciphers are populated", async () => { overlayBackground["inlineMenuCiphers"] = new Map([ - ["inline-menu-cipher-0", mock({ type: CipherType.Login })], + [ + "inline-menu-cipher-0", + mock({ + type: CipherType.Login, + login: { + username: "username1", + password: "password1", + uri: "https://example.com", + }, + }), + ], ]); await overlayBackground["getInlineMenuCipherData"](); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 8b577ccccf5..58e462943bf 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -66,6 +66,7 @@ import { InlineMenuFormFieldData } from "../services/abstractions/autofill-overl import { AutofillService, PageDetail } from "../services/abstractions/autofill.service"; import { InlineMenuFieldQualificationService } from "../services/abstractions/inline-menu-field-qualifications.service"; import { + areKeyValuesNull, generateDomainMatchPatterns, generateRandomChars, isInvalidResponseStatusCode, @@ -556,6 +557,28 @@ export class OverlayBackground implements OverlayBackgroundInterface { for (let cipherIndex = 0; cipherIndex < inlineMenuCiphersArray.length; cipherIndex++) { const [inlineMenuCipherId, cipher] = inlineMenuCiphersArray[cipherIndex]; + + switch (cipher.type) { + case CipherType.Card: + if (areKeyValuesNull(cipher.card)) { + continue; + } + break; + + case CipherType.Identity: + if (areKeyValuesNull(cipher.identity)) { + continue; + } + break; + + case CipherType.Login: + if ( + areKeyValuesNull(cipher.login, ["username", "password", "totp", "fido2Credentials"]) + ) { + continue; + } + break; + } if (!this.focusedFieldMatchesFillType(cipher.type)) { continue; } diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 5922e26e11b..12d26914d82 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -544,3 +544,20 @@ export const specialCharacterToKeyMap: Record = { "?": "questionCharacterDescriptor", "/": "forwardSlashCharacterDescriptor", }; + +/** + * Checks if all the values corresponding to the specified keys in an object are null. + * If no keys are specified, checks all keys in the object. + * + * @param obj - The object to check. + * @param keys - An optional array of keys to check in the object. Defaults to all keys. + * @returns Returns true if all values for the specified keys (or all keys if none are provided) are null; otherwise, false. + */ +export function areKeyValuesNull>( + obj: T, + keys?: Array, +): boolean { + const keysToCheck = keys && keys.length > 0 ? keys : (Object.keys(obj) as Array); + + return keysToCheck.every((key) => obj[key] == null); +} From 72121cda948f08fcf71f1a55220a29a482fda01c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 8 Jan 2025 10:46:00 +0100 Subject: [PATCH 087/270] [PM-10741] Refactor biometrics interface & add dynamic status (#10973) --- apps/browser/src/_locales/en/messages.json | 27 ++ .../account-switching/account.component.ts | 5 + .../settings/account-security.component.html | 7 +- .../settings/account-security.component.ts | 49 +++- .../browser/src/background/main.background.ts | 14 +- .../background/nativeMessaging.background.ts | 240 ++++++++++-------- .../src/background/runtime.background.ts | 23 +- .../background-browser-biometrics.service.ts | 140 ++++++++-- .../biometrics/browser-biometrics.service.ts | 19 -- .../foreground-browser-biometrics.ts | 47 +++- .../src/key-management/browser-key.service.ts | 91 ------- .../extension-lock-component.service.spec.ts | 50 ++-- .../extension-lock-component.service.ts | 85 ++----- .../src/popup/services/services.module.ts | 11 +- .../safari/SafariWebExtensionHandler.swift | 212 +++++++++++++++- .../key-management/cli-biometrics-service.ts | 27 ++ .../service-container/service-container.ts | 8 +- .../src/app/accounts/settings.component.ts | 51 ++-- .../app/layout/account-switcher.component.ts | 4 + .../src/app/services/services.module.ts | 12 +- .../biometrics/biometric.noop.main.ts | 44 ---- .../biometric.renderer-ipc.listener.ts | 65 ----- .../biometrics/biometrics.service.spec.ts | 138 +++++++--- .../biometrics/biometrics.service.ts | 212 ---------------- .../biometrics/desktop.biometrics.service.ts | 59 +---- .../biometrics/electron-biometrics.service.ts | 38 --- .../src/key-management/biometrics/index.ts | 2 - .../main-biometrics-ipc.listener.ts | 63 +++++ .../biometrics/main-biometrics.service.ts | 167 ++++++++++++ ...main.ts => os-biometrics-linux.service.ts} | 4 +- ...n.main.ts => os-biometrics-mac.service.ts} | 4 +- ...in.ts => os-biometrics-windows.service.ts} | 21 +- .../biometrics/os-biometrics.service.ts | 32 +++ .../biometrics/renderer-biometrics.service.ts | 54 ++++ .../desktop-lock-component.service.spec.ts | 134 ++-------- .../desktop-lock-component.service.ts | 92 ++----- apps/desktop/src/key-management/preload.ts | 50 +++- apps/desktop/src/locales/en/messages.json | 24 ++ apps/desktop/src/main.ts | 32 ++- apps/desktop/src/main/tray.main.ts | 7 + .../models/native-messaging/legacy-message.ts | 1 + .../desktop-credential-storage-listener.ts | 33 +-- apps/desktop/src/platform/preload.ts | 1 + .../services/electron-key.service.spec.ts | 115 --------- .../platform/services/electron-key.service.ts | 64 ++--- .../biometric-message-handler.service.spec.ts | 123 +++++++++ .../biometric-message-handler.service.ts | 172 +++++++++++-- apps/desktop/src/types/biometric-message.ts | 18 +- .../web-lock-component.service.spec.ts | 3 +- .../services/web-lock-component.service.ts | 3 +- .../key-management/web-biometric.service.ts | 36 +-- .../src/services/jslib-services.module.ts | 68 ++--- .../user-verification.service.spec.ts | 37 +-- .../user-verification.service.ts | 50 ++-- .../default-process-reload.service.ts | 2 + .../platform/enums/key-suffix-options.enum.ts | 1 - .../vault-timeout.service.spec.ts | 4 + .../vault-timeout/vault-timeout.service.ts | 4 + libs/key-management/src/angular/index.ts | 6 +- .../lock/components/lock.component.html | 6 +- .../angular/lock/components/lock.component.ts | 92 ++++++- .../lock/services/lock-component.service.ts | 9 +- .../src/biometrics/biometric.service.ts | 49 ++-- .../src/biometrics/biometrics-commands.ts | 14 + .../src/biometrics/biometrics-status.ts | 22 ++ libs/key-management/src/index.ts | 2 + 66 files changed, 1840 insertions(+), 1459 deletions(-) delete mode 100644 apps/browser/src/key-management/biometrics/browser-biometrics.service.ts delete mode 100644 apps/browser/src/key-management/browser-key.service.ts create mode 100644 apps/cli/src/key-management/cli-biometrics-service.ts delete mode 100644 apps/desktop/src/key-management/biometrics/biometric.noop.main.ts delete mode 100644 apps/desktop/src/key-management/biometrics/biometric.renderer-ipc.listener.ts delete mode 100644 apps/desktop/src/key-management/biometrics/biometrics.service.ts delete mode 100644 apps/desktop/src/key-management/biometrics/electron-biometrics.service.ts delete mode 100644 apps/desktop/src/key-management/biometrics/index.ts create mode 100644 apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts create mode 100644 apps/desktop/src/key-management/biometrics/main-biometrics.service.ts rename apps/desktop/src/key-management/biometrics/{biometric.unix.main.ts => os-biometrics-linux.service.ts} (97%) rename apps/desktop/src/key-management/biometrics/{biometric.darwin.main.ts => os-biometrics-mac.service.ts} (92%) rename apps/desktop/src/key-management/biometrics/{biometric.windows.main.ts => os-biometrics-windows.service.ts} (93%) create mode 100644 apps/desktop/src/key-management/biometrics/os-biometrics.service.ts create mode 100644 apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts delete mode 100644 apps/desktop/src/platform/services/electron-key.service.spec.ts create mode 100644 apps/desktop/src/services/biometric-message-handler.service.spec.ts create mode 100644 libs/key-management/src/biometrics/biometrics-commands.ts create mode 100644 libs/key-management/src/biometrics/biometrics-status.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 85937b63304..55c9ae8616b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4656,6 +4656,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/auth/popup/account-switching/account.component.ts b/apps/browser/src/auth/popup/account-switching/account.component.ts index 104241e9c7b..dad74977d34 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account.component.ts @@ -8,6 +8,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AvatarModule, ItemModule } from "@bitwarden/components"; +import { BiometricsService } from "@bitwarden/key-management"; import { AccountSwitcherService, AvailableAccount } from "./services/account-switcher.service"; @@ -26,6 +27,7 @@ export class AccountComponent { private location: Location, private i18nService: I18nService, private logService: LogService, + private biometricsService: BiometricsService, ) {} get specialAccountAddId() { @@ -45,6 +47,9 @@ export class AccountComponent { // locked or logged out account statuses are handled by background and app.component if (result?.status === AuthenticationStatus.Unlocked) { this.location.back(); + await this.biometricsService.setShouldAutopromptNow(false); + } else { + await this.biometricsService.setShouldAutopromptNow(true); } this.loading.emit(false); } 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 e0dfde7be77..3f874fc1a76 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -11,13 +11,16 @@

    {{ "unlockMethods" | i18n }}

    - + {{ "unlockWithBiometrics" | i18n }} + + {{ biometricUnavailabilityReason }} + - + { + const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id); + const biometricSettingAvailable = + (status !== BiometricsStatus.DesktopDisconnected && + status !== BiometricsStatus.NotEnabledInConnectedDesktopApp) || + (await this.vaultTimeoutSettingsService.isBiometricLockSet()); + if (!biometricSettingAvailable) { + this.form.controls.biometric.disable({ emitEvent: false }); + } else { + this.form.controls.biometric.enable({ emitEvent: false }); + } + + if (status === BiometricsStatus.DesktopDisconnected && !biometricSettingAvailable) { + this.biometricUnavailabilityReason = this.i18nService.t( + "biometricsStatusHelptextDesktopDisconnected", + ); + } else if ( + status === BiometricsStatus.NotEnabledInConnectedDesktopApp && + !biometricSettingAvailable + ) { + this.biometricUnavailabilityReason = this.i18nService.t( + "biometricsStatusHelptextNotEnabledInDesktop", + activeAccount.email, + ); + } else { + this.biometricUnavailabilityReason = ""; + } + }), + takeUntil(this.destroy$), + ) + .subscribe(); + this.showChangeMasterPass = await this.userVerificationService.hasMasterPassword(); this.form.controls.vaultTimeout.valueChanges @@ -399,7 +438,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { } async updateBiometric(enabled: boolean) { - if (enabled && this.supportsBiometric) { + if (enabled) { let granted; try { granted = await BrowserApi.requestPermission({ permissions: ["nativeMessaging"] }); @@ -471,7 +510,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { const biometricsPromise = async () => { try { - const result = await this.biometricsService.authenticateBiometric(); + const result = await this.biometricsService.authenticateWithBiometrics(); // prevent duplicate dialog biometricsResponseReceived = true; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index bcfa797e0ff..4bec3d6cc0a 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -204,6 +204,7 @@ import { BiometricStateService, BiometricsService, DefaultBiometricStateService, + DefaultKeyService, DefaultKdfConfigService, KdfConfigService, KeyService as KeyServiceAbstraction, @@ -241,7 +242,6 @@ import AutofillService from "../autofill/services/autofill.service"; import { InlineMenuFieldQualificationService } from "../autofill/services/inline-menu-field-qualification.service"; import { SafariApp } from "../browser/safariApp"; import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service"; -import { BrowserKeyService } from "../key-management/browser-key.service"; import { BrowserApi } from "../platform/browser/browser-api"; import { flagEnabled } from "../platform/flags"; import { UpdateBadge } from "../platform/listeners/update-badge"; @@ -416,6 +416,7 @@ export default class MainBackground { await this.refreshMenu(true); if (this.systemService != null) { await this.systemService.clearPendingClipboard(); + await this.biometricsService.setShouldAutopromptNow(false); await this.processReloadService.startProcessReload(this.authService); } }; @@ -633,6 +634,7 @@ export default class MainBackground { this.biometricsService = new BackgroundBrowserBiometricsService( runtimeNativeMessagingBackground, + this.logService, ); this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); @@ -649,7 +651,7 @@ export default class MainBackground { this.stateService, ); - this.keyService = new BrowserKeyService( + this.keyService = new DefaultKeyService( this.pinService, this.masterPasswordService, this.keyGenerationService, @@ -660,8 +662,6 @@ export default class MainBackground { this.stateService, this.accountService, this.stateProvider, - this.biometricStateService, - this.biometricsService, this.kdfConfigService, ); @@ -857,10 +857,8 @@ export default class MainBackground { this.userVerificationApiService, this.userDecryptionOptionsService, this.pinService, - this.logService, - this.vaultTimeoutSettingsService, - this.platformUtilsService, this.kdfConfigService, + this.biometricsService, ); this.vaultFilterService = new VaultFilterService( @@ -890,6 +888,7 @@ export default class MainBackground { this.stateEventRunnerService, this.taskSchedulerService, this.logService, + this.biometricsService, lockedCallback, logoutCallback, ); @@ -1081,6 +1080,7 @@ export default class MainBackground { this.vaultTimeoutSettingsService, this.biometricStateService, this.accountService, + this.logService, ); // Other fields diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 2ded1760235..116d048d2e8 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -1,10 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { delay, filter, firstValueFrom, from, map, race, timer } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -14,18 +13,19 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserKey } from "@bitwarden/common/types/key"; -import { KeyService, BiometricStateService } from "@bitwarden/key-management"; +import { KeyService, BiometricStateService, BiometricsCommands } from "@bitwarden/key-management"; import { BrowserApi } from "../platform/browser/browser-api"; import RuntimeBackground from "./runtime.background"; const MessageValidTimeout = 10 * 1000; +const MessageNoResponseTimeout = 60 * 1000; const HashAlgorithmForEncryption = "sha1"; type Message = { command: string; + messageId?: number; // Filled in by this service userId?: string; @@ -43,6 +43,7 @@ type OuterMessage = { type ReceiveMessage = { timestamp: number; command: string; + messageId: number; response?: any; // Unlock key @@ -53,19 +54,23 @@ type ReceiveMessage = { type ReceiveMessageOuter = { command: string; appId: string; + messageId?: number; // Should only have one of these. message?: EncString; sharedSecret?: string; }; +type Callback = { + resolver: any; + rejecter: any; +}; + export class NativeMessagingBackground { - private connected = false; + connected = false; private connecting: boolean; private port: browser.runtime.Port | chrome.runtime.Port; - private resolver: any = null; - private rejecter: any = null; private privateKey: Uint8Array = null; private publicKey: Uint8Array = null; private secureSetupResolve: any = null; @@ -73,6 +78,11 @@ export class NativeMessagingBackground { private appId: string; private validatingFingerprint: boolean; + private messageId = 0; + private callbacks = new Map(); + + isConnectedToOutdatedDesktopClient = true; + constructor( private keyService: KeyService, private encryptService: EncryptService, @@ -97,6 +107,7 @@ export class NativeMessagingBackground { } async connect() { + this.logService.info("[Native Messaging IPC] Connecting to Bitwarden Desktop app..."); this.appId = await this.appIdService.getAppId(); await this.biometricStateService.setFingerprintValidated(false); @@ -106,6 +117,9 @@ export class NativeMessagingBackground { this.connecting = true; const connectedCallback = () => { + this.logService.info( + "[Native Messaging IPC] Connection to Bitwarden Desktop app established!", + ); this.connected = true; this.connecting = false; resolve(); @@ -123,11 +137,17 @@ export class NativeMessagingBackground { connectedCallback(); break; case "disconnected": + this.logService.info("[Native Messaging IPC] Disconnected from Bitwarden Desktop app."); if (this.connecting) { reject(new Error("startDesktop")); } this.connected = false; this.port.disconnect(); + // reject all + for (const callback of this.callbacks.values()) { + callback.rejecter("disconnected"); + } + this.callbacks.clear(); break; case "setupEncryption": { // Ignore since it belongs to another device @@ -147,6 +167,16 @@ export class NativeMessagingBackground { await this.biometricStateService.setFingerprintValidated(true); } this.sharedSecret = new SymmetricCryptoKey(decrypted); + this.logService.info("[Native Messaging IPC] Secure channel established"); + + if ("messageId" in message) { + this.logService.info("[Native Messaging IPC] Non-legacy desktop client"); + this.isConnectedToOutdatedDesktopClient = false; + } else { + this.logService.info("[Native Messaging IPC] Legacy desktop client"); + this.isConnectedToOutdatedDesktopClient = true; + } + this.secureSetupResolve(); break; } @@ -155,17 +185,25 @@ export class NativeMessagingBackground { if (message.appId !== this.appId) { return; } + this.logService.warning( + "[Native Messaging IPC] Secure channel encountered an error; disconnecting and wiping keys...", + ); this.sharedSecret = null; this.privateKey = null; this.connected = false; - this.rejecter({ - message: "invalidateEncryption", - }); + if (this.callbacks.has(message.messageId)) { + this.callbacks.get(message.messageId).rejecter({ + message: "invalidateEncryption", + }); + } return; case "verifyFingerprint": { if (this.sharedSecret == null) { + this.logService.info( + "[Native Messaging IPC] Desktop app requested trust verification by fingerprint.", + ); this.validatingFingerprint = true; // 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 @@ -174,9 +212,11 @@ export class NativeMessagingBackground { break; } case "wrongUserId": - this.rejecter({ - message: "wrongUserId", - }); + if (this.callbacks.has(message.messageId)) { + this.callbacks.get(message.messageId).rejecter({ + message: "wrongUserId", + }); + } return; default: // Ignore since it belongs to another device @@ -210,6 +250,60 @@ export class NativeMessagingBackground { }); } + async callCommand(message: Message): Promise { + const messageId = this.messageId++; + + if ( + message.command == BiometricsCommands.Unlock || + message.command == BiometricsCommands.IsAvailable + ) { + // TODO remove after 2025.01 + // wait until there is no other callbacks, or timeout + const call = await firstValueFrom( + race( + from([false]).pipe(delay(5000)), + timer(0, 100).pipe( + filter(() => this.callbacks.size === 0), + map(() => true), + ), + ), + ); + if (!call) { + this.logService.info( + `[Native Messaging IPC] Message of type ${message.command} did not get a response before timing out`, + ); + return; + } + } + + const callback = new Promise((resolver, rejecter) => { + this.callbacks.set(messageId, { resolver, rejecter }); + }); + message.messageId = messageId; + try { + await this.send(message); + } catch (e) { + this.logService.info( + `[Native Messaging IPC] Error sending message of type ${message.command} to Bitwarden Desktop app. Error: ${e}`, + ); + const callback = this.callbacks.get(messageId); + this.callbacks.delete(messageId); + callback.rejecter("errorConnecting"); + } + + setTimeout(() => { + if (this.callbacks.has(messageId)) { + this.logService.info("[Native Messaging IPC] Message timed out and received no response"); + this.callbacks.get(messageId).rejecter({ + message: "timeout", + }); + this.callbacks.delete(messageId); + } + }, MessageNoResponseTimeout); + + return callback; + } + async send(message: Message) { if (!this.connected) { await this.connect(); @@ -233,20 +327,7 @@ export class NativeMessagingBackground { return await this.encryptService.encrypt(JSON.stringify(message), this.sharedSecret); } - getResponse(): Promise { - return new Promise((resolve, reject) => { - this.resolver = function (response: any) { - resolve(response); - }; - this.rejecter = function (resp: any) { - reject({ - message: resp, - }); - }; - }); - } - - private postMessage(message: OuterMessage) { + private postMessage(message: OuterMessage, messageId?: number) { // Wrap in try-catch to when the port disconnected without triggering `onDisconnect`. try { const msg: any = message; @@ -262,13 +343,17 @@ export class NativeMessagingBackground { } this.port.postMessage(msg); } catch (e) { - this.logService.error("NativeMessaging port disconnected, disconnecting."); + this.logService.info( + "[Native Messaging IPC] Disconnected from Bitwarden Desktop app because of the native port disconnecting.", + ); this.sharedSecret = null; this.privateKey = null; this.connected = false; - this.rejecter("invalidateEncryption"); + if (this.callbacks.has(messageId)) { + this.callbacks.get(messageId).rejecter("invalidateEncryption"); + } } } @@ -285,90 +370,30 @@ export class NativeMessagingBackground { } if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { - this.logService.error("NativeMessage is to old, ignoring."); + this.logService.info("[Native Messaging IPC] Received an old native message, ignoring..."); return; } - switch (message.command) { - case "biometricUnlock": { - if ( - ["not available", "not enabled", "not supported", "not unlocked", "canceled"].includes( - message.response, - ) - ) { - this.rejecter(message.response); - return; - } + const messageId = message.messageId; - // Check for initial setup of biometric unlock - const enabled = await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$); - if (enabled === null || enabled === false) { - if (message.response === "unlocked") { - await this.biometricStateService.setBiometricUnlockEnabled(true); - } - break; - } - - // Ignore unlock if already unlocked - if ((await this.authService.getAuthStatus()) === AuthenticationStatus.Unlocked) { - break; - } - - if (message.response === "unlocked") { - try { - if (message.userKeyB64) { - const userKey = new SymmetricCryptoKey( - Utils.fromB64ToArray(message.userKeyB64), - ) as UserKey; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const isUserKeyValid = await this.keyService.validateUserKey(userKey, activeUserId); - if (isUserKeyValid) { - await this.keyService.setUserKey(userKey, activeUserId); - } else { - this.logService.error("Unable to verify biometric unlocked userkey"); - await this.keyService.clearKeys(activeUserId); - this.rejecter("userkey wrong"); - return; - } - } else { - throw new Error("No key received"); - } - } catch (e) { - this.logService.error("Unable to set key: " + e); - this.rejecter("userkey wrong"); - return; - } - - // Verify key is correct by attempting to decrypt a secret - try { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.keyService.getFingerprint(userId); - } catch (e) { - this.logService.error("Unable to verify key: " + e); - await this.keyService.clearKeys(); - this.rejecter("userkey wrong"); - return; - } - - // 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.runtimeBackground.processMessage({ command: "unlocked" }); - } - break; - } - case "biometricUnlockAvailable": { - this.resolver(message); - break; - } - default: - this.logService.error("NativeMessage, got unknown command: " + message.command); - break; + if ( + message.command == BiometricsCommands.Unlock || + message.command == BiometricsCommands.IsAvailable + ) { + this.logService.info( + `[Native Messaging IPC] Received legacy message of type ${message.command}`, + ); + const messageId = this.callbacks.keys().next().value; + const resolver = this.callbacks.get(messageId); + this.callbacks.delete(messageId); + resolver.resolver(message); + return; } - if (this.resolver) { - this.resolver(message); + if (this.callbacks.has(messageId)) { + this.callbacks.get(messageId).resolver(message); + } else { + this.logService.info("[Native Messaging IPC] Received message without a callback", message); } } @@ -384,6 +409,7 @@ export class NativeMessagingBackground { command: "setupEncryption", publicKey: Utils.fromBufferToB64(publicKey), userId: userId, + messageId: this.messageId++, }); return new Promise((resolve, reject) => (this.secureSetupResolve = resolve)); diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index c31ec94be90..75340e3fbc3 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -16,6 +16,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { BiometricsCommands } from "@bitwarden/key-management"; import { MessageListener, isExternalMessage } from "../../../../libs/common/src/platform/messaging"; import { @@ -71,8 +72,10 @@ export default class RuntimeBackground { sendResponse: (response: any) => void, ) => { const messagesWithResponse = [ - "biometricUnlock", - "biometricUnlockAvailable", + BiometricsCommands.AuthenticateWithBiometrics, + BiometricsCommands.GetBiometricsStatus, + BiometricsCommands.UnlockWithBiometricsForUser, + BiometricsCommands.GetBiometricsStatusForUser, "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag", "getInlineMenuFieldQualificationFeatureFlag", "getInlineMenuTotpFeatureFlag", @@ -185,13 +188,17 @@ export default class RuntimeBackground { break; } break; - case "biometricUnlock": { - const result = await this.main.biometricsService.authenticateBiometric(); - return result; + case BiometricsCommands.AuthenticateWithBiometrics: { + return await this.main.biometricsService.authenticateWithBiometrics(); } - case "biometricUnlockAvailable": { - const result = await this.main.biometricsService.isBiometricUnlockAvailable(); - return result; + case BiometricsCommands.GetBiometricsStatus: { + return await this.main.biometricsService.getBiometricsStatus(); + } + case BiometricsCommands.UnlockWithBiometricsForUser: { + return await this.main.biometricsService.unlockWithBiometricsForUser(msg.userId); + } + case BiometricsCommands.GetBiometricsStatusForUser: { + return await this.main.biometricsService.getBiometricsStatusForUser(msg.userId); } case "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag": { return await this.configService.getFeatureFlag( diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 0cd48c45938..8e6fc562d14 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -1,36 +1,136 @@ import { Injectable } from "@angular/core"; -import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsService, BiometricsCommands, BiometricsStatus } from "@bitwarden/key-management"; -import { BrowserBiometricsService } from "./browser-biometrics.service"; +import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; +import { BrowserApi } from "../../platform/browser/browser-api"; @Injectable() -export class BackgroundBrowserBiometricsService extends BrowserBiometricsService { - constructor(private nativeMessagingBackground: () => NativeMessagingBackground) { +export class BackgroundBrowserBiometricsService extends BiometricsService { + constructor( + private nativeMessagingBackground: () => NativeMessagingBackground, + private logService: LogService, + ) { super(); } - async authenticateBiometric(): Promise { - const responsePromise = this.nativeMessagingBackground().getResponse(); - await this.nativeMessagingBackground().send({ command: "biometricUnlock" }); - const response = await responsePromise; - return response.response === "unlocked"; + async authenticateWithBiometrics(): Promise { + try { + await this.ensureConnected(); + + if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.Unlock, + }); + return response.response == "unlocked"; + } else { + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.AuthenticateWithBiometrics, + }); + return response.response; + } + } catch (e) { + this.logService.info("Biometric authentication failed", e); + return false; + } } - async isBiometricUnlockAvailable(): Promise { - const responsePromise = this.nativeMessagingBackground().getResponse(); - await this.nativeMessagingBackground().send({ command: "biometricUnlockAvailable" }); - const response = await responsePromise; - return response.response === "available"; + async getBiometricsStatus(): Promise { + if (!(await BrowserApi.permissionsGranted(["nativeMessaging"]))) { + return BiometricsStatus.NativeMessagingPermissionMissing; + } + + try { + await this.ensureConnected(); + + if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.IsAvailable, + }); + const resp = + response.response == "available" + ? BiometricsStatus.Available + : BiometricsStatus.HardwareUnavailable; + return resp; + } else { + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.GetBiometricsStatus, + }); + + if (response.response) { + return response.response; + } + } + return BiometricsStatus.Available; + } catch (e) { + return BiometricsStatus.DesktopDisconnected; + } } - async biometricsNeedsSetup(): Promise { + async unlockWithBiometricsForUser(userId: UserId): Promise { + try { + await this.ensureConnected(); + + if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.Unlock, + }); + if (response.response == "unlocked") { + return response.userKeyB64; + } else { + return null; + } + } else { + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.UnlockWithBiometricsForUser, + userId: userId, + }); + if (response.response) { + return response.userKeyB64; + } else { + return null; + } + } + } catch (e) { + this.logService.info("Biometric unlock for user failed", e); + throw new Error("Biometric unlock failed"); + } + } + + async getBiometricsStatusForUser(id: UserId): Promise { + try { + await this.ensureConnected(); + + if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { + return await this.getBiometricsStatus(); + } + + return ( + await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.GetBiometricsStatusForUser, + userId: id, + }) + ).response; + } catch (e) { + return BiometricsStatus.DesktopDisconnected; + } + } + + // the first time we call, this might use an outdated version of the protocol, so we drop the response + private async ensureConnected() { + if (!this.nativeMessagingBackground().connected) { + await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.IsAvailable, + }); + } + } + + async getShouldAutopromptNow(): Promise { return false; } - async biometricsSupportsAutoSetup(): Promise { - return false; - } - - async biometricsSetup(): Promise {} + async setShouldAutopromptNow(value: boolean): Promise {} } diff --git a/apps/browser/src/key-management/biometrics/browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/browser-biometrics.service.ts deleted file mode 100644 index 7ffbed45415..00000000000 --- a/apps/browser/src/key-management/biometrics/browser-biometrics.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { BiometricsService } from "@bitwarden/key-management"; - -import { BrowserApi } from "../../platform/browser/browser-api"; - -@Injectable() -export abstract class BrowserBiometricsService extends BiometricsService { - async supportsBiometric() { - const platformInfo = await BrowserApi.getPlatformInfo(); - if (platformInfo.os === "mac" || platformInfo.os === "win" || platformInfo.os === "linux") { - return true; - } - return false; - } - - abstract authenticateBiometric(): Promise; - abstract isBiometricUnlockAvailable(): Promise; -} diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index f50468c8b7a..0235ad5bd9c 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -1,34 +1,55 @@ +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsCommands, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; + import { BrowserApi } from "../../platform/browser/browser-api"; -import { BrowserBiometricsService } from "./browser-biometrics.service"; +export class ForegroundBrowserBiometricsService extends BiometricsService { + shouldAutopromptNow = true; -export class ForegroundBrowserBiometricsService extends BrowserBiometricsService { - async authenticateBiometric(): Promise { + async authenticateWithBiometrics(): Promise { const response = await BrowserApi.sendMessageWithResponse<{ result: boolean; error: string; - }>("biometricUnlock"); + }>(BiometricsCommands.AuthenticateWithBiometrics); if (!response.result) { throw response.error; } return response.result; } - async isBiometricUnlockAvailable(): Promise { + async getBiometricsStatus(): Promise { const response = await BrowserApi.sendMessageWithResponse<{ - result: boolean; + result: BiometricsStatus; error: string; - }>("biometricUnlockAvailable"); - return response.result && response.result === true; + }>(BiometricsCommands.GetBiometricsStatus); + return response.result; } - async biometricsNeedsSetup(): Promise { - return false; + async unlockWithBiometricsForUser(userId: UserId): Promise { + const response = await BrowserApi.sendMessageWithResponse<{ + result: string; + error: string; + }>(BiometricsCommands.UnlockWithBiometricsForUser, { userId }); + if (!response.result) { + return null; + } + return SymmetricCryptoKey.fromString(response.result) as UserKey; } - async biometricsSupportsAutoSetup(): Promise { - return false; + async getBiometricsStatusForUser(id: UserId): Promise { + const response = await BrowserApi.sendMessageWithResponse<{ + result: BiometricsStatus; + error: string; + }>(BiometricsCommands.GetBiometricsStatusForUser, { userId: id }); + return response.result; } - async biometricsSetup(): Promise {} + async getShouldAutopromptNow(): Promise { + return this.shouldAutopromptNow; + } + async setShouldAutopromptNow(value: boolean): Promise { + this.shouldAutopromptNow = value; + } } diff --git a/apps/browser/src/key-management/browser-key.service.ts b/apps/browser/src/key-management/browser-key.service.ts deleted file mode 100644 index 0cc5f13a27e..00000000000 --- a/apps/browser/src/key-management/browser-key.service.ts +++ /dev/null @@ -1,91 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { firstValueFrom } from "rxjs"; - -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; -import { USER_KEY } from "@bitwarden/common/platform/services/key-state/user-key.state"; -import { StateProvider } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey } from "@bitwarden/common/types/key"; -import { - KdfConfigService, - DefaultKeyService, - BiometricsService, - BiometricStateService, -} from "@bitwarden/key-management"; - -export class BrowserKeyService extends DefaultKeyService { - constructor( - pinService: PinServiceAbstraction, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - keyGenerationService: KeyGenerationService, - cryptoFunctionService: CryptoFunctionService, - encryptService: EncryptService, - platformUtilService: PlatformUtilsService, - logService: LogService, - stateService: StateService, - accountService: AccountService, - stateProvider: StateProvider, - private biometricStateService: BiometricStateService, - private biometricsService: BiometricsService, - kdfConfigService: KdfConfigService, - ) { - super( - pinService, - masterPasswordService, - keyGenerationService, - cryptoFunctionService, - encryptService, - platformUtilService, - logService, - stateService, - accountService, - stateProvider, - kdfConfigService, - ); - } - override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const biometricUnlockPromise = - userId == null - ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) - : this.biometricStateService.getBiometricUnlockEnabled(userId); - return await biometricUnlockPromise; - } - return super.hasUserKeyStored(keySuffix, userId); - } - - /** - * Browser doesn't store biometric keys, so we retrieve them from the desktop and return - * if we successfully saved it into memory as the User Key - * @returns the `UserKey` if the user passes a biometrics prompt, otherwise return `null`. - */ - protected override async getKeyFromStorage( - keySuffix: KeySuffixOptions, - userId?: UserId, - ): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const biometricsResult = await this.biometricsService.authenticateBiometric(); - - if (!biometricsResult) { - return null; - } - - const userKey = await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId)); - if (userKey) { - return userKey; - } - } - - return await super.getKeyFromStorage(keySuffix, userId); - } -} diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts index 272201c6ede..4b0323d5ebe 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts @@ -9,8 +9,8 @@ import { import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; -import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/key-management/angular"; +import { KeyService, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { UnlockOptions } from "@bitwarden/key-management/angular"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; @@ -121,8 +121,7 @@ describe("ExtensionLockComponentService", () => { describe("getAvailableUnlockOptions$", () => { interface MockInputs { hasMasterPassword: boolean; - osSupportsBiometric: boolean; - biometricLockSet: boolean; + biometricsStatusForUser: BiometricsStatus; hasBiometricEncryptedUserKeyStored: boolean; platformSupportsSecureStorage: boolean; pinDecryptionAvailable: boolean; @@ -133,8 +132,7 @@ describe("ExtensionLockComponentService", () => { // MP + PIN + Biometrics available { hasMasterPassword: true, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: true, @@ -148,7 +146,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -156,8 +154,7 @@ describe("ExtensionLockComponentService", () => { // PIN + Biometrics available { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: true, @@ -171,7 +168,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -179,8 +176,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics available: user key stored with no secure storage { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: false, pinDecryptionAvailable: false, @@ -194,7 +190,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -202,8 +198,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics available: no user key stored with no secure storage { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.Available, hasBiometricEncryptedUserKeyStored: false, platformSupportsSecureStorage: false, pinDecryptionAvailable: false, @@ -217,7 +212,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -225,8 +220,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics not available: biometric lock not set { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: false, + biometricsStatusForUser: BiometricsStatus.UnlockNeeded, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: false, @@ -240,7 +234,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, + biometricsStatus: BiometricsStatus.UnlockNeeded, }, }, ], @@ -248,8 +242,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics not available: user key not stored { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.NotEnabledInConnectedDesktopApp, hasBiometricEncryptedUserKeyStored: false, platformSupportsSecureStorage: true, pinDecryptionAvailable: false, @@ -263,7 +256,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, + biometricsStatus: BiometricsStatus.NotEnabledInConnectedDesktopApp, }, }, ], @@ -271,8 +264,7 @@ describe("ExtensionLockComponentService", () => { // Biometrics not available: OS doesn't support { hasMasterPassword: false, - osSupportsBiometric: false, - biometricLockSet: true, + biometricsStatusForUser: BiometricsStatus.HardwareUnavailable, hasBiometricEncryptedUserKeyStored: true, platformSupportsSecureStorage: true, pinDecryptionAvailable: false, @@ -286,7 +278,7 @@ describe("ExtensionLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.NotSupportedOnOperatingSystem, + biometricsStatus: BiometricsStatus.HardwareUnavailable, }, }, ], @@ -304,8 +296,12 @@ describe("ExtensionLockComponentService", () => { ); // Biometrics - biometricsService.supportsBiometric.mockResolvedValue(mockInputs.osSupportsBiometric); - vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(mockInputs.biometricLockSet); + biometricsService.getBiometricsStatusForUser.mockResolvedValue( + mockInputs.biometricsStatusForUser, + ); + vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue( + mockInputs.hasBiometricEncryptedUserKeyStored, + ); keyService.hasUserKeyStored.mockResolvedValue(mockInputs.hasBiometricEncryptedUserKeyStored); platformUtilsService.supportsSecureStorage.mockReturnValue( mockInputs.platformSupportsSecureStorage, diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts index 07fb2ec6b87..f21beb91cff 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -7,27 +7,17 @@ import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; -import { - LockComponentService, - BiometricsDisableReason, - UnlockOptions, -} from "@bitwarden/key-management/angular"; +import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; export class ExtensionLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); - private readonly platformUtilsService = inject(PlatformUtilsService); private readonly biometricsService = inject(BiometricsService); private readonly pinService = inject(PinServiceAbstraction); - private readonly vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService); - private readonly keyService = inject(KeyService); private readonly routerService = inject(BrowserRouterService); getPreviousUrl(): string | null { @@ -52,67 +42,28 @@ export class ExtensionLockComponentService implements LockComponentService { return "unlockWithBiometrics"; } - private async isBiometricLockSet(userId: UserId): Promise { - const biometricLockSet = await this.vaultTimeoutSettingsService.isBiometricLockSet(userId); - const hasBiometricEncryptedUserKeyStored = await this.keyService.hasUserKeyStored( - KeySuffixOptions.Biometric, - userId, - ); - const platformSupportsSecureStorage = this.platformUtilsService.supportsSecureStorage(); - - return ( - biometricLockSet && (hasBiometricEncryptedUserKeyStored || !platformSupportsSecureStorage) - ); - } - - private getBiometricsDisabledReason( - osSupportsBiometric: boolean, - biometricLockSet: boolean, - ): BiometricsDisableReason | null { - if (!osSupportsBiometric) { - return BiometricsDisableReason.NotSupportedOnOperatingSystem; - } else if (!biometricLockSet) { - return BiometricsDisableReason.EncryptedKeysUnavailable; - } - - return null; - } - getAvailableUnlockOptions$(userId: UserId): Observable { return combineLatest([ // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to - defer(() => this.biometricsService.supportsBiometric()), - defer(() => this.isBiometricLockSet(userId)), + defer(async () => await this.biometricsService.getBiometricsStatusForUser(userId)), this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), defer(() => this.pinService.isPinDecryptionAvailable(userId)), ]).pipe( - map( - ([ - supportsBiometric, - isBiometricsLockSet, - userDecryptionOptions, - pinDecryptionAvailable, - ]) => { - const disableReason = this.getBiometricsDisabledReason( - supportsBiometric, - isBiometricsLockSet, - ); - - const unlockOpts: UnlockOptions = { - masterPassword: { - enabled: userDecryptionOptions.hasMasterPassword, - }, - pin: { - enabled: pinDecryptionAvailable, - }, - biometrics: { - enabled: supportsBiometric && isBiometricsLockSet, - disableReason: disableReason, - }, - }; - return unlockOpts; - }, - ), + map(([biometricsStatus, userDecryptionOptions, pinDecryptionAvailable]) => { + const unlockOpts: UnlockOptions = { + masterPassword: { + enabled: userDecryptionOptions.hasMasterPassword, + }, + pin: { + enabled: pinDecryptionAvailable, + }, + biometrics: { + enabled: biometricsStatus === BiometricsStatus.Available, + biometricsStatus: biometricsStatus, + }, + }; + return unlockOpts; + }), ); } } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 6542eb9c814..0fb21732fdd 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -111,8 +111,8 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac import { KdfConfigService, KeyService, - BiometricStateService, BiometricsService, + DefaultKeyService, } from "@bitwarden/key-management"; import { LockComponentService } from "@bitwarden/key-management/angular"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -126,7 +126,6 @@ import { AutofillService as AutofillServiceAbstraction } from "../../autofill/se import AutofillService from "../../autofill/services/autofill.service"; import { InlineMenuFieldQualificationService } from "../../autofill/services/inline-menu-field-qualification.service"; import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics"; -import { BrowserKeyService } from "../../key-management/browser-key.service"; import { ExtensionLockComponentService } from "../../key-management/lock/services/extension-lock-component.service"; import { BrowserApi } from "../../platform/browser/browser-api"; import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator"; @@ -232,11 +231,9 @@ const safeProviders: SafeProvider[] = [ stateService: StateService, accountService: AccountServiceAbstraction, stateProvider: StateProvider, - biometricStateService: BiometricStateService, - biometricsService: BiometricsService, kdfConfigService: KdfConfigService, ) => { - const keyService = new BrowserKeyService( + const keyService = new DefaultKeyService( pinService, masterPasswordService, keyGenerationService, @@ -247,8 +244,6 @@ const safeProviders: SafeProvider[] = [ stateService, accountService, stateProvider, - biometricStateService, - biometricsService, kdfConfigService, ); new ContainerService(keyService, encryptService).attachToGlobal(self); @@ -265,8 +260,6 @@ const safeProviders: SafeProvider[] = [ StateService, AccountServiceAbstraction, StateProvider, - BiometricStateService, - BiometricsService, KdfConfigService, ], }), diff --git a/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift b/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift index 1768ce6b15f..58d95f959be 100644 --- a/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift +++ b/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift @@ -86,8 +86,203 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { context.completeRequest(returningItems: [response], completionHandler: nil) } return - case "biometricUnlock": + case "authenticateWithBiometrics": + let messageId = message?["messageId"] as? Int + let laContext = LAContext() + guard let accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage, .userPresence], nil) else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "authenticateWithBiometrics", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + break + } + laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "authenticate") { (success, error) in + if success { + response.userInfo = [ SFExtensionMessageKey: [ + "message": [ + "command": "authenticateWithBiometrics", + "response": true, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ]] + } else { + response.userInfo = [ SFExtensionMessageKey: [ + "message": [ + "command": "authenticateWithBiometrics", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ]] + } + context.completeRequest(returningItems: [response], completionHandler: nil) + } + return + case "getBiometricsStatus": + let messageId = message?["messageId"] as? Int + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatus", + "response": BiometricsStatus.Available.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil); + break + case "unlockWithBiometricsForUser": + let messageId = message?["messageId"] as? Int + var error: NSError? + let laContext = LAContext() + + laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) + + if let e = error, e.code != kLAErrorBiometryLockout { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil) + break + } + + guard let accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage, .userPresence], nil) else { + let messageId = message?["messageId"] as? Int + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": false, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil) + break + } + laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "unlock your vault") { (success, error) in + if success { + guard let userId = message?["userId"] as? String else { + return + } + let passwordName = userId + "_user_biometric" + var passwordLength: UInt32 = 0 + var passwordPtr: UnsafeMutableRawPointer? = nil + + var status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil) + if status != errSecSuccess { + let fallbackName = "key" + status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil) + } + + if status == errSecSuccess { + let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String? + SecKeychainItemFreeContent(nil, passwordPtr) + + response.userInfo = [ SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": true, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "userKeyB64": result!.replacingOccurrences(of: "\"", with: ""), + "messageId": messageId, + ], + ]] + } else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": true, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + } + } + + context.completeRequest(returningItems: [response], completionHandler: nil) + } + return + case "getBiometricsStatusForUser": + let messageId = message?["messageId"] as? Int + let laContext = LAContext() + if !laContext.isBiometricsAvailable() { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatusForUser", + "response": BiometricsStatus.HardwareUnavailable.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + + context.completeRequest(returningItems: [response], completionHandler: nil) + break + } + + guard let userId = message?["userId"] as? String else { + return + } + let passwordName = userId + "_user_biometric" + var passwordLength: UInt32 = 0 + var passwordPtr: UnsafeMutableRawPointer? = nil + + var status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil) + if status != errSecSuccess { + let fallbackName = "key" + status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil) + } + + if status == errSecSuccess { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatusForUser", + "response": BiometricsStatus.Available.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + } else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "getBiometricsStatusForUser", + "response": BiometricsStatus.NotEnabledInConnectedDesktopApp.rawValue, + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "messageId": messageId, + ], + ], + ] + } + break + case "biometricUnlock": + var error: NSError? let laContext = LAContext() if(!laContext.isBiometricsAvailable()){ @@ -115,7 +310,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { ] break } - laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "Bitwarden Safari Extension") { (success, error) in + laContext.evaluateAccessControl(accessControl, operation: .useKeySign, localizedReason: "Biometric Unlock") { (success, error) in if success { guard let userId = message?["userId"] as? String else { return @@ -157,7 +352,6 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { context.completeRequest(returningItems: [response], completionHandler: nil) } - return case "biometricUnlockAvailable": let laContext = LAContext() @@ -228,3 +422,15 @@ class DownloadFileMessage: Decodable, Encodable { class DownloadFileMessageBlobOptions: Decodable, Encodable { var type: String? } + +enum BiometricsStatus : Int { + case Available = 0 + case UnlockNeeded = 1 + case HardwareUnavailable = 2 + case AutoSetupNeeded = 3 + case ManualSetupNeeded = 4 + case PlatformUnsupported = 5 + case DesktopDisconnected = 6 + case NotEnabledLocally = 7 + case NotEnabledInConnectedDesktopApp = 8 +} diff --git a/apps/cli/src/key-management/cli-biometrics-service.ts b/apps/cli/src/key-management/cli-biometrics-service.ts new file mode 100644 index 00000000000..bda8fe82895 --- /dev/null +++ b/apps/cli/src/key-management/cli-biometrics-service.ts @@ -0,0 +1,27 @@ +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; + +export class CliBiometricsService extends BiometricsService { + async authenticateWithBiometrics(): Promise { + return false; + } + + async getBiometricsStatus(): Promise { + return BiometricsStatus.PlatformUnsupported; + } + + async unlockWithBiometricsForUser(userId: UserId): Promise { + return null; + } + + async getBiometricsStatusForUser(userId: UserId): Promise { + return BiometricsStatus.PlatformUnsupported; + } + + async getShouldAutopromptNow(): Promise { + return false; + } + + async setShouldAutopromptNow(value: boolean): Promise {} +} diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index bef4d52fad5..f57db9909d6 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -165,6 +165,7 @@ import { VaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; +import { CliBiometricsService } from "../key-management/cli-biometrics-service"; import { flagEnabled } from "../platform/flags"; import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service"; import { ConsoleLogService } from "../platform/services/console-log.service"; @@ -693,12 +694,12 @@ export class ServiceContainer { this.userVerificationApiService, this.userDecryptionOptionsService, this.pinService, - this.logService, - this.vaultTimeoutSettingsService, - this.platformUtilsService, this.kdfConfigService, + new CliBiometricsService(), ); + const biometricService = new CliBiometricsService(); + this.vaultTimeoutService = new VaultTimeoutService( this.accountService, this.masterPasswordService, @@ -714,6 +715,7 @@ export class ServiceContainer { this.stateEventRunnerService, this.taskSchedulerService, this.logService, + biometricService, lockedCallback, undefined, ); diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index c27ca240d3f..19748e797bb 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -22,7 +22,7 @@ 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 { KeySuffixOptions, ThemeType } from "@bitwarden/common/platform/enums"; +import { ThemeType } from "@bitwarden/common/platform/enums/theme-type.enum"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -32,10 +32,11 @@ import { VaultTimeoutStringType, } from "@bitwarden/common/types/vault-timeout.type"; import { DialogService } from "@bitwarden/components"; -import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; +import { KeyService, BiometricStateService, BiometricsStatus } from "@bitwarden/key-management"; import { SetPinComponent } from "../../auth/components/set-pin.component"; import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; +import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { NativeMessagingManifestService } from "../services/native-messaging-manifest.service"; @@ -54,6 +55,7 @@ export class SettingsComponent implements OnInit, OnDestroy { themeOptions: any[]; clearClipboardOptions: any[]; supportsBiometric: boolean; + private timerId: any; showAlwaysShowDock = false; requireEnableTray = false; showDuckDuckGoIntegrationOption = false; @@ -139,7 +141,7 @@ export class SettingsComponent implements OnInit, OnDestroy { private userVerificationService: UserVerificationServiceAbstraction, private desktopSettingsService: DesktopSettingsService, private biometricStateService: BiometricStateService, - private biometricsService: BiometricsService, + private biometricsService: DesktopBiometricsService, private desktopAutofillSettingsService: DesktopAutofillSettingsService, private pinService: PinServiceAbstraction, private logService: LogService, @@ -297,7 +299,6 @@ export class SettingsComponent implements OnInit, OnDestroy { // Non-form values this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop; this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop; - this.supportsBiometric = await this.biometricsService.supportsBiometric(); this.previousVaultTimeout = this.form.value.vaultTimeout; this.refreshTimeoutSettings$ @@ -360,6 +361,13 @@ export class SettingsComponent implements OnInit, OnDestroy { this.form.controls.enableBrowserIntegrationFingerprint.disable(); } }); + + this.supportsBiometric = + (await this.biometricsService.getBiometricsStatus()) === BiometricsStatus.Available; + this.timerId = setInterval(async () => { + this.supportsBiometric = + (await this.biometricsService.getBiometricsStatus()) === BiometricsStatus.Available; + }, 1000); } async saveVaultTimeout(newValue: VaultTimeout) { @@ -476,23 +484,20 @@ export class SettingsComponent implements OnInit, OnDestroy { return; } - const needsSetup = await this.biometricsService.biometricsNeedsSetup(); - const supportsBiometricAutoSetup = await this.biometricsService.biometricsSupportsAutoSetup(); + const status = await this.biometricsService.getBiometricsStatus(); - if (needsSetup) { - if (supportsBiometricAutoSetup) { - await this.biometricsService.biometricsSetup(); - } else { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "biometricsManualSetupTitle" }, - content: { key: "biometricsManualSetupDesc" }, - type: "warning", - }); - if (confirmed) { - this.platformUtilsService.launchUri("https://bitwarden.com/help/biometrics/"); - } - return; + if (status === BiometricsStatus.AutoSetupNeeded) { + await this.biometricsService.setupBiometrics(); + } else if (status === BiometricsStatus.ManualSetupNeeded) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "biometricsManualSetupTitle" }, + content: { key: "biometricsManualSetupDesc" }, + type: "warning", + }); + if (confirmed) { + this.platformUtilsService.launchUri("https://bitwarden.com/help/biometrics/"); } + return; } await this.biometricStateService.setBiometricUnlockEnabled(true); @@ -513,8 +518,13 @@ export class SettingsComponent implements OnInit, OnDestroy { } await this.keyService.refreshAdditionalKeys(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // Validate the key is stored in case biometrics fail. - const biometricSet = await this.keyService.hasUserKeyStored(KeySuffixOptions.Biometric); + const biometricSet = + (await this.biometricsService.getBiometricsStatusForUser(activeUserId)) === + BiometricsStatus.Available; this.form.controls.biometric.setValue(biometricSet, { emitEvent: false }); if (!biometricSet) { await this.biometricStateService.setBiometricUnlockEnabled(false); @@ -779,6 +789,7 @@ export class SettingsComponent implements OnInit, OnDestroy { ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); + clearInterval(this.timerId); } get biometricText() { diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index cbd0dcf78aa..db8c2a85bde 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -17,6 +17,8 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; import { UserId } from "@bitwarden/common/types/guid"; +import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; + type ActiveAccount = { id: string; name: string; @@ -90,6 +92,7 @@ export class AccountSwitcherComponent implements OnInit { private environmentService: EnvironmentService, private loginEmailService: LoginEmailServiceAbstraction, private accountService: AccountService, + private biometricsService: DesktopBiometricsService, ) { this.activeAccount$ = this.accountService.activeAccount$.pipe( switchMap(async (active) => { @@ -181,6 +184,7 @@ export class AccountSwitcherComponent implements OnInit { async switch(userId: string) { this.close(); + await this.biometricsService.setShouldAutopromptNow(true); this.disabled = true; const accountSwitchFinishedPromise = firstValueFrom( diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 87c2a833073..8b890032443 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -102,7 +102,8 @@ import { DesktopLoginComponentService } from "../../auth/login/desktop-login-com import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; import { DesktopAutofillService } from "../../autofill/services/desktop-autofill.service"; import { DesktopFido2UserInterfaceService } from "../../autofill/services/desktop-fido2-user-interface.service"; -import { ElectronBiometricsService } from "../../key-management/biometrics/electron-biometrics.service"; +import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; +import { RendererBiometricsService } from "../../key-management/biometrics/renderer-biometrics.service"; import { DesktopLockComponentService } from "../../key-management/lock/services/desktop-lock-component.service"; import { flagEnabled } from "../../platform/flags"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; @@ -142,7 +143,12 @@ const safeProviders: SafeProvider[] = [ safeProvider(InitService), safeProvider({ provide: BiometricsService, - useClass: ElectronBiometricsService, + useClass: RendererBiometricsService, + deps: [], + }), + safeProvider({ + provide: DesktopBiometricsService, + useClass: RendererBiometricsService, deps: [], }), safeProvider(NativeMessagingService), @@ -241,6 +247,7 @@ const safeProviders: SafeProvider[] = [ VaultTimeoutSettingsService, BiometricStateService, AccountServiceAbstraction, + LogService, ], }), safeProvider({ @@ -302,6 +309,7 @@ const safeProviders: SafeProvider[] = [ StateProvider, BiometricStateService, KdfConfigService, + DesktopBiometricsService, ], }), safeProvider({ diff --git a/apps/desktop/src/key-management/biometrics/biometric.noop.main.ts b/apps/desktop/src/key-management/biometrics/biometric.noop.main.ts deleted file mode 100644 index 57a86942e8c..00000000000 --- a/apps/desktop/src/key-management/biometrics/biometric.noop.main.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { OsBiometricService } from "./desktop.biometrics.service"; - -export default class NoopBiometricsService implements OsBiometricService { - constructor() {} - - async init() {} - - async osSupportsBiometric(): Promise { - return false; - } - - async osBiometricsNeedsSetup(): Promise { - return false; - } - - async osBiometricsCanAutoSetup(): Promise { - return false; - } - - async osBiometricsSetup(): Promise {} - - async getBiometricKey( - service: string, - storageKey: string, - clientKeyHalfB64: string, - ): Promise { - return null; - } - - async setBiometricKey( - service: string, - storageKey: string, - value: string, - clientKeyPartB64: string | undefined, - ): Promise { - return; - } - - async deleteBiometricKey(service: string, key: string): Promise {} - - async authenticateBiometric(): Promise { - throw new Error("Not supported on this platform"); - } -} diff --git a/apps/desktop/src/key-management/biometrics/biometric.renderer-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/biometric.renderer-ipc.listener.ts deleted file mode 100644 index a057deca54f..00000000000 --- a/apps/desktop/src/key-management/biometrics/biometric.renderer-ipc.listener.ts +++ /dev/null @@ -1,65 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ipcMain } from "electron"; - -import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; - -import { BiometricMessage, BiometricAction } from "../../types/biometric-message"; - -import { DesktopBiometricsService } from "./desktop.biometrics.service"; - -export class BiometricsRendererIPCListener { - constructor( - private serviceName: string, - private biometricService: DesktopBiometricsService, - private logService: ConsoleLogService, - ) {} - - init() { - ipcMain.handle("biometric", async (event: any, message: BiometricMessage) => { - try { - let serviceName = this.serviceName; - message.keySuffix = "_" + (message.keySuffix ?? ""); - if (message.keySuffix !== "_") { - serviceName += message.keySuffix; - } - - let val: string | boolean = null; - - if (!message.action) { - return val; - } - - switch (message.action) { - case BiometricAction.EnabledForUser: - if (!message.key || !message.userId) { - break; - } - val = await this.biometricService.canAuthBiometric({ - service: serviceName, - key: message.key, - userId: message.userId, - }); - break; - case BiometricAction.OsSupported: - val = await this.biometricService.supportsBiometric(); - break; - case BiometricAction.NeedsSetup: - val = await this.biometricService.biometricsNeedsSetup(); - break; - case BiometricAction.Setup: - await this.biometricService.biometricsSetup(); - break; - case BiometricAction.CanAutoSetup: - val = await this.biometricService.biometricsSupportsAutoSetup(); - break; - default: - } - - return val; - } catch (e) { - this.logService.info(e); - } - }); - } -} diff --git a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts index d2ed648ba65..e69ebca3630 100644 --- a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts +++ b/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts @@ -4,14 +4,19 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { BiometricStateService } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; import { WindowMain } from "../../main/window.main"; -import BiometricDarwinMain from "./biometric.darwin.main"; -import BiometricWindowsMain from "./biometric.windows.main"; -import { BiometricsService } from "./biometrics.service"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { MainBiometricsService } from "./main-biometrics.service"; +import OsBiometricsServiceLinux from "./os-biometrics-linux.service"; +import OsBiometricsServiceMac from "./os-biometrics-mac.service"; +import OsBiometricsServiceWindows from "./os-biometrics-windows.service"; +import { OsBiometricService } from "./os-biometrics.service"; jest.mock("@bitwarden/desktop-napi", () => { return { @@ -28,8 +33,7 @@ describe("biometrics tests", function () { const biometricStateService = mock(); it("Should call the platformspecific methods", async () => { - const userId = "userId-1" as UserId; - const sut = new BiometricsService( + const sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -39,21 +43,15 @@ describe("biometrics tests", function () { ); const mockService = mock(); - (sut as any).platformSpecificService = mockService; - await sut.setEncryptionKeyHalf({ service: "test", key: "test", value: "test" }); + (sut as any).osBiometricsService = mockService; - await sut.canAuthBiometric({ service: "test", key: "test", userId }); - expect(mockService.osSupportsBiometric).toBeCalled(); - - // 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 - sut.authenticateBiometric(); + await sut.authenticateBiometric(); expect(mockService.authenticateBiometric).toBeCalled(); }); describe("Should create a platform specific service", function () { it("Should create a biometrics service specific for Windows", () => { - const sut = new BiometricsService( + const sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -62,13 +60,13 @@ describe("biometrics tests", function () { biometricStateService, ); - const internalService = (sut as any).platformSpecificService; + const internalService = (sut as any).osBiometricsService; expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(BiometricWindowsMain); + expect(internalService).toBeInstanceOf(OsBiometricsServiceWindows); }); it("Should create a biometrics service specific for MacOs", () => { - const sut = new BiometricsService( + const sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -76,19 +74,33 @@ describe("biometrics tests", function () { "darwin", biometricStateService, ); - const internalService = (sut as any).platformSpecificService; + const internalService = (sut as any).osBiometricsService; expect(internalService).not.toBeNull(); - expect(internalService).toBeInstanceOf(BiometricDarwinMain); + expect(internalService).toBeInstanceOf(OsBiometricsServiceMac); + }); + + it("Should create a biometrics service specific for Linux", () => { + const sut = new MainBiometricsService( + i18nService, + windowMain, + logService, + messagingService, + "linux", + biometricStateService, + ); + + const internalService = (sut as any).osBiometricsService; + expect(internalService).not.toBeNull(); + expect(internalService).toBeInstanceOf(OsBiometricsServiceLinux); }); }); describe("can auth biometric", () => { let sut: BiometricsService; let innerService: MockProxy; - const userId = "userId-1" as UserId; beforeEach(() => { - sut = new BiometricsService( + sut = new MainBiometricsService( i18nService, windowMain, logService, @@ -98,34 +110,78 @@ describe("biometrics tests", function () { ); innerService = mock(); - (sut as any).platformSpecificService = innerService; + (sut as any).osBiometricsService = innerService; }); - it("should return false if client key half is required and not provided", async () => { - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true); + it("should return the correct biometric status for system status", async () => { + const testCases = [ + // happy path + [true, false, false, BiometricsStatus.Available], + [false, true, true, BiometricsStatus.AutoSetupNeeded], + [false, true, false, BiometricsStatus.ManualSetupNeeded], + [false, false, false, BiometricsStatus.HardwareUnavailable], - const result = await sut.canAuthBiometric({ service: "test", key: "test", userId }); + // should not happen + [false, false, true, BiometricsStatus.HardwareUnavailable], + [true, true, true, BiometricsStatus.Available], + [true, true, false, BiometricsStatus.Available], + [true, false, true, BiometricsStatus.Available], + ]; - expect(result).toBe(false); + for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) { + innerService.osSupportsBiometric.mockResolvedValue(supportsBiometric as boolean); + innerService.osBiometricsNeedsSetup.mockResolvedValue(needsSetup as boolean); + innerService.osBiometricsCanAutoSetup.mockResolvedValue(canAutoSetup as boolean); + + const actual = await sut.getBiometricsStatus(); + expect(actual).toBe(expected); + } }); - it("should call osSupportsBiometric if client key half is provided", 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 - sut.setEncryptionKeyHalf({ service: "test", key: "test", value: "test" }); + it("should return the correct biometric status for user status", async () => { + const testCases = [ + // system status, biometric unlock enabled, require password on start, has key half, result + [BiometricsStatus.Available, false, false, false, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, true, false, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, false, true, BiometricsStatus.NotEnabledLocally], + [BiometricsStatus.Available, false, true, true, BiometricsStatus.NotEnabledLocally], - await sut.canAuthBiometric({ service: "test", key: "test", userId }); - expect(innerService.osSupportsBiometric).toBeCalled(); - }); + [ + BiometricsStatus.PlatformUnsupported, + true, + true, + true, + BiometricsStatus.PlatformUnsupported, + ], + [BiometricsStatus.ManualSetupNeeded, true, true, true, BiometricsStatus.ManualSetupNeeded], + [BiometricsStatus.AutoSetupNeeded, true, true, true, BiometricsStatus.AutoSetupNeeded], - it("should call osSupportBiometric if client key half is not required", async () => { - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(false); - innerService.osSupportsBiometric.mockResolvedValue(true); + [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], + [BiometricsStatus.Available, true, true, false, BiometricsStatus.UnlockNeeded], + [BiometricsStatus.Available, true, false, true, BiometricsStatus.Available], + ]; - const result = await sut.canAuthBiometric({ service: "test", key: "test", userId }); + for (const [ + systemStatus, + unlockEnabled, + requirePasswordOnStart, + hasKeyHalf, + expected, + ] of testCases) { + sut.getBiometricsStatus = jest.fn().mockResolvedValue(systemStatus as BiometricsStatus); + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(unlockEnabled as boolean); + biometricStateService.getRequirePasswordOnStart.mockResolvedValue( + requirePasswordOnStart as boolean, + ); + (sut as any).clientKeyHalves = new Map(); + const userId = "test" as UserId; + if (hasKeyHalf) { + (sut as any).clientKeyHalves.set(userId, "test"); + } - expect(result).toBe(true); - expect(innerService.osSupportsBiometric).toHaveBeenCalled(); + const actual = await sut.getBiometricsStatusForUser(userId); + expect(actual).toBe(expected); + } }); }); }); diff --git a/apps/desktop/src/key-management/biometrics/biometrics.service.ts b/apps/desktop/src/key-management/biometrics/biometrics.service.ts deleted file mode 100644 index 3867412d884..00000000000 --- a/apps/desktop/src/key-management/biometrics/biometrics.service.ts +++ /dev/null @@ -1,212 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { 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 { UserId } from "@bitwarden/common/types/guid"; -import { BiometricStateService } from "@bitwarden/key-management"; - -import { WindowMain } from "../../main/window.main"; - -import { DesktopBiometricsService, OsBiometricService } from "./desktop.biometrics.service"; - -export class BiometricsService extends DesktopBiometricsService { - private platformSpecificService: OsBiometricService; - private clientKeyHalves = new Map(); - - constructor( - private i18nService: I18nService, - private windowMain: WindowMain, - private logService: LogService, - private messagingService: MessagingService, - private platform: NodeJS.Platform, - private biometricStateService: BiometricStateService, - ) { - super(); - this.loadPlatformSpecificService(this.platform); - } - - private loadPlatformSpecificService(platform: NodeJS.Platform) { - if (platform === "win32") { - this.loadWindowsHelloService(); - } else if (platform === "darwin") { - this.loadMacOSService(); - } else if (platform === "linux") { - this.loadUnixService(); - } else { - this.loadNoopBiometricsService(); - } - } - - private loadWindowsHelloService() { - // eslint-disable-next-line - const BiometricWindowsMain = require("./biometric.windows.main").default; - this.platformSpecificService = new BiometricWindowsMain( - this.i18nService, - this.windowMain, - this.logService, - ); - } - - private loadMacOSService() { - // eslint-disable-next-line - const BiometricDarwinMain = require("./biometric.darwin.main").default; - this.platformSpecificService = new BiometricDarwinMain(this.i18nService); - } - - private loadUnixService() { - // eslint-disable-next-line - const BiometricUnixMain = require("./biometric.unix.main").default; - this.platformSpecificService = new BiometricUnixMain(this.i18nService, this.windowMain); - } - - private loadNoopBiometricsService() { - // eslint-disable-next-line - const NoopBiometricsService = require("./biometric.noop.main").default; - this.platformSpecificService = new NoopBiometricsService(); - } - - async supportsBiometric() { - return await this.platformSpecificService.osSupportsBiometric(); - } - - async biometricsNeedsSetup() { - return await this.platformSpecificService.osBiometricsNeedsSetup(); - } - - async biometricsSupportsAutoSetup() { - return await this.platformSpecificService.osBiometricsCanAutoSetup(); - } - - async biometricsSetup() { - await this.platformSpecificService.osBiometricsSetup(); - } - - async canAuthBiometric({ - service, - key, - userId, - }: { - service: string; - key: string; - userId: UserId; - }): Promise { - const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); - const clientKeyHalfB64 = this.getClientKeyHalf(service, key); - const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64; - return clientKeyHalfSatisfied && (await this.supportsBiometric()); - } - - async authenticateBiometric(): Promise { - let result = 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.interruptProcessReload( - () => { - return this.platformSpecificService.authenticateBiometric(); - }, - (response) => { - result = response; - return !response; - }, - ); - return result; - } - - async isBiometricUnlockAvailable(): Promise { - return await this.platformSpecificService.osSupportsBiometric(); - } - - async getBiometricKey(service: string, storageKey: string): Promise { - return await this.interruptProcessReload(async () => { - await this.enforceClientKeyHalf(service, storageKey); - - return await this.platformSpecificService.getBiometricKey( - service, - storageKey, - this.getClientKeyHalf(service, storageKey), - ); - }); - } - - async setBiometricKey(service: string, storageKey: string, value: string): Promise { - await this.enforceClientKeyHalf(service, storageKey); - - return await this.platformSpecificService.setBiometricKey( - service, - storageKey, - value, - this.getClientKeyHalf(service, storageKey), - ); - } - - /** Registers the client-side encryption key half for the OS stored Biometric key. The other half is protected by the OS.*/ - async setEncryptionKeyHalf({ - service, - key, - value, - }: { - service: string; - key: string; - value: string; - }): Promise { - if (value == null) { - this.clientKeyHalves.delete(this.clientKeyHalfKey(service, key)); - } else { - this.clientKeyHalves.set(this.clientKeyHalfKey(service, key), value); - } - } - - async deleteBiometricKey(service: string, storageKey: string): Promise { - this.clientKeyHalves.delete(this.clientKeyHalfKey(service, storageKey)); - return await this.platformSpecificService.deleteBiometricKey(service, storageKey); - } - - private async interruptProcessReload( - callback: () => Promise, - restartReloadCallback: (arg: T) => boolean = () => false, - ): Promise { - this.messagingService.send("cancelProcessReload"); - let restartReload = false; - let response: T; - try { - response = await callback(); - restartReload ||= restartReloadCallback(response); - } catch (error) { - if (error.message === "Biometric authentication failed") { - restartReload = false; - } else { - restartReload = true; - } - } - - if (restartReload) { - this.messagingService.send("startProcessReload"); - } - - return response; - } - - private clientKeyHalfKey(service: string, key: string): string { - return `${service}:${key}`; - } - - private getClientKeyHalf(service: string, key: string): string | undefined { - return this.clientKeyHalves.get(this.clientKeyHalfKey(service, key)) ?? undefined; - } - - private async enforceClientKeyHalf(service: string, storageKey: string): Promise { - // The first half of the storageKey is the userId, separated by `_` - // We need to extract from the service because the active user isn't properly synced to the main process, - // So we can't use the observables on `biometricStateService` - const [userId] = storageKey.split("_"); - const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart( - userId as UserId, - ); - const clientKeyHalfB64 = this.getClientKeyHalf(service, storageKey); - - if (requireClientKeyHalf && !clientKeyHalfB64) { - throw new Error("Biometric key requirements not met. No client key half provided."); - } - } -} diff --git a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts index eee3e5fc7f3..0c0efea78f9 100644 --- a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts @@ -1,3 +1,4 @@ +import { UserId } from "@bitwarden/common/types/guid"; import { BiometricsService } from "@bitwarden/key-management"; /** @@ -5,58 +6,10 @@ import { BiometricsService } from "@bitwarden/key-management"; * specifically for the main process. */ export abstract class DesktopBiometricsService extends BiometricsService { - abstract canAuthBiometric({ - service, - key, - userId, - }: { - service: string; - key: string; - userId: string; - }): Promise; - abstract getBiometricKey(service: string, key: string): Promise; - abstract setBiometricKey(service: string, key: string, value: string): Promise; - abstract setEncryptionKeyHalf({ - service, - key, - value, - }: { - service: string; - key: string; - value: string; - }): void; - abstract deleteBiometricKey(service: string, key: string): Promise; -} + abstract setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise; + abstract deleteBiometricUnlockKeyForUser(userId: UserId): Promise; -export interface OsBiometricService { - osSupportsBiometric(): Promise; - /** - * Check whether support for biometric unlock requires setup. This can be automatic or manual. - * - * @returns true if biometrics support requires setup, false if it does not (is already setup, or did not require it in the first place) - */ - osBiometricsNeedsSetup: () => Promise; - /** - * Check whether biometrics can be automatically setup, or requires user interaction. - * - * @returns true if biometrics support can be automatically setup, false if it requires user interaction. - */ - osBiometricsCanAutoSetup: () => Promise; - /** - * Starts automatic biometric setup, which places the required configuration files / changes the required settings. - */ - osBiometricsSetup: () => Promise; - authenticateBiometric(): Promise; - getBiometricKey( - service: string, - key: string, - clientKeyHalfB64: string | undefined, - ): Promise; - setBiometricKey( - service: string, - key: string, - value: string, - clientKeyHalfB64: string | undefined, - ): Promise; - deleteBiometricKey(service: string, key: string): Promise; + abstract setupBiometrics(): Promise; + + abstract setClientKeyHalfForUser(userId: UserId, value: string): Promise; } diff --git a/apps/desktop/src/key-management/biometrics/electron-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/electron-biometrics.service.ts deleted file mode 100644 index 226c914e6ff..00000000000 --- a/apps/desktop/src/key-management/biometrics/electron-biometrics.service.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { BiometricsService } from "@bitwarden/key-management"; - -/** - * This service implement the base biometrics service to provide desktop specific functions, - * specifically for the renderer process by passing messages to the main process. - */ -@Injectable() -export class ElectronBiometricsService extends BiometricsService { - async supportsBiometric(): Promise { - return await ipc.keyManagement.biometric.osSupported(); - } - - async isBiometricUnlockAvailable(): Promise { - return await ipc.keyManagement.biometric.osSupported(); - } - - /** This method is used to authenticate the user presence _only_. - * It should not be used in the process to retrieve - * biometric keys, which has a separate authentication mechanism. - * For biometric keys, invoke "keytar" with a biometric key suffix */ - async authenticateBiometric(): Promise { - return await ipc.keyManagement.biometric.authenticate(); - } - - async biometricsNeedsSetup(): Promise { - return await ipc.keyManagement.biometric.biometricsNeedsSetup(); - } - - async biometricsSupportsAutoSetup(): Promise { - return await ipc.keyManagement.biometric.biometricsCanAutoSetup(); - } - - async biometricsSetup(): Promise { - return await ipc.keyManagement.biometric.biometricsSetup(); - } -} diff --git a/apps/desktop/src/key-management/biometrics/index.ts b/apps/desktop/src/key-management/biometrics/index.ts deleted file mode 100644 index ad7725d718a..00000000000 --- a/apps/desktop/src/key-management/biometrics/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./desktop.biometrics.service"; -export * from "./biometrics.service"; diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts new file mode 100644 index 00000000000..eebafd8d48b --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts @@ -0,0 +1,63 @@ +import { ipcMain } from "electron"; + +import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { BiometricMessage, BiometricAction } from "../../types/biometric-message"; + +import { DesktopBiometricsService } from "./desktop.biometrics.service"; + +export class MainBiometricsIPCListener { + constructor( + private biometricService: DesktopBiometricsService, + private logService: ConsoleLogService, + ) {} + + init() { + ipcMain.handle("biometric", async (event: any, message: BiometricMessage) => { + try { + if (!message.action) { + return; + } + + switch (message.action) { + case BiometricAction.Authenticate: + return await this.biometricService.authenticateWithBiometrics(); + case BiometricAction.GetStatus: + return await this.biometricService.getBiometricsStatus(); + case BiometricAction.UnlockForUser: + return await this.biometricService.unlockWithBiometricsForUser( + message.userId as UserId, + ); + case BiometricAction.GetStatusForUser: + return await this.biometricService.getBiometricsStatusForUser(message.userId as UserId); + case BiometricAction.SetKeyForUser: + return await this.biometricService.setBiometricProtectedUnlockKeyForUser( + message.userId as UserId, + message.key, + ); + case BiometricAction.RemoveKeyForUser: + return await this.biometricService.deleteBiometricUnlockKeyForUser( + message.userId as UserId, + ); + case BiometricAction.SetClientKeyHalf: + return await this.biometricService.setClientKeyHalfForUser( + message.userId as UserId, + message.key, + ); + case BiometricAction.Setup: + return await this.biometricService.setupBiometrics(); + + case BiometricAction.SetShouldAutoprompt: + return await this.biometricService.setShouldAutopromptNow(message.data as boolean); + case BiometricAction.GetShouldAutoprompt: + return await this.biometricService.getShouldAutopromptNow(); + default: + return; + } + } catch (e) { + this.logService.info(e); + } + }); + } +} diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts new file mode 100644 index 00000000000..06956503a05 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -0,0 +1,167 @@ +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 { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-management"; + +import { WindowMain } from "../../main/window.main"; + +import { DesktopBiometricsService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; + +export class MainBiometricsService extends DesktopBiometricsService { + private osBiometricsService: OsBiometricService; + private clientKeyHalves = new Map(); + private shouldAutoPrompt = true; + + constructor( + private i18nService: I18nService, + private windowMain: WindowMain, + private logService: LogService, + private messagingService: MessagingService, + private platform: NodeJS.Platform, + private biometricStateService: BiometricStateService, + ) { + super(); + this.loadOsBiometricService(this.platform); + } + + private loadOsBiometricService(platform: NodeJS.Platform) { + if (platform === "win32") { + // eslint-disable-next-line + const OsBiometricsServiceWindows = require("./os-biometrics-windows.service").default; + this.osBiometricsService = new OsBiometricsServiceWindows( + this.i18nService, + this.windowMain, + this.logService, + ); + } else if (platform === "darwin") { + // eslint-disable-next-line + const OsBiometricsServiceMac = require("./os-biometrics-mac.service").default; + this.osBiometricsService = new OsBiometricsServiceMac(this.i18nService); + } else if (platform === "linux") { + // eslint-disable-next-line + const OsBiometricsServiceLinux = require("./os-biometrics-linux.service").default; + this.osBiometricsService = new OsBiometricsServiceLinux(this.i18nService, this.windowMain); + } else { + throw new Error("Unsupported platform"); + } + } + + /** + * Get the status of biometrics for the platform. Biometrics status for the platform can be one of: + * - Available: Biometrics are available and can be used (On windows hello, (touch id (for now)) and polkit, this MAY fall back to password) + * - HardwareUnavailable: Biometrics are not available on the platform + * - ManualSetupNeeded: In order to use biometrics, the user must perform manual steps (linux only) + * - AutoSetupNeeded: In order to use biometrics, the user must perform automatic steps (linux only) + * @returns the status of the biometrics of the platform + */ + async getBiometricsStatus(): Promise { + if (!(await this.osBiometricsService.osSupportsBiometric())) { + if (await this.osBiometricsService.osBiometricsNeedsSetup()) { + if (await this.osBiometricsService.osBiometricsCanAutoSetup()) { + return BiometricsStatus.AutoSetupNeeded; + } else { + return BiometricsStatus.ManualSetupNeeded; + } + } + + return BiometricsStatus.HardwareUnavailable; + } + return BiometricsStatus.Available; + } + + /** + * Get the status of biometric unlock for a specific user. For this, biometric unlock needs to be set up for the user in the settings. + * Next, biometrics unlock needs to be available on the platform level. If "masterpassword reprompt" is enabled, a client key half (set on first unlock) for this user + * needs to be held in memory. + * @param userId the user to check the biometric unlock status for + * @returns the status of the biometric unlock for the user + */ + async getBiometricsStatusForUser(userId: UserId): Promise { + if (!(await this.biometricStateService.getBiometricUnlockEnabled(userId))) { + return BiometricsStatus.NotEnabledLocally; + } + + const platformStatus = await this.getBiometricsStatus(); + if (!(platformStatus === BiometricsStatus.Available)) { + return platformStatus; + } + + const requireClientKeyHalf = await this.biometricStateService.getRequirePasswordOnStart(userId); + const clientKeyHalfB64 = this.clientKeyHalves.get(userId); + const clientKeyHalfSatisfied = !requireClientKeyHalf || !!clientKeyHalfB64; + if (!clientKeyHalfSatisfied) { + return BiometricsStatus.UnlockNeeded; + } + + return BiometricsStatus.Available; + } + + async authenticateBiometric(): Promise { + return await this.osBiometricsService.authenticateBiometric(); + } + + async setupBiometrics(): Promise { + return await this.osBiometricsService.osBiometricsSetup(); + } + + async setClientKeyHalfForUser(userId: UserId, value: string): Promise { + this.clientKeyHalves.set(userId, value); + } + + async authenticateWithBiometrics(): Promise { + return await this.osBiometricsService.authenticateBiometric(); + } + + async unlockWithBiometricsForUser(userId: UserId): Promise { + return SymmetricCryptoKey.fromString( + await this.osBiometricsService.getBiometricKey( + "Bitwarden_biometric", + `${userId}_user_biometric`, + this.clientKeyHalves.get(userId), + ), + ) as UserKey; + } + + async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise { + const service = "Bitwarden_biometric"; + const storageKey = `${userId}_user_biometric`; + if (!this.clientKeyHalves.has(userId)) { + throw new Error("No client key half provided for user"); + } + + return await this.osBiometricsService.setBiometricKey( + service, + storageKey, + value, + this.clientKeyHalves.get(userId), + ); + } + + async deleteBiometricUnlockKeyForUser(userId: UserId): Promise { + return await this.osBiometricsService.deleteBiometricKey( + "Bitwarden_biometric", + `${userId}_user_biometric`, + ); + } + + /** + * Set whether to auto-prompt the user for biometric unlock; this can be used to prevent auto-prompting being initiated by a process reload. + * Reasons for enabling auto prompt include: Starting the app, un-minimizing the app, manually account switching + * @param value Whether to auto-prompt the user for biometric unlock + */ + async setShouldAutopromptNow(value: boolean): Promise { + this.shouldAutoPrompt = value; + } + + /** + * Get whether to auto-prompt the user for biometric unlock; If the user is auto-prompted, setShouldAutopromptNow should be immediately called with false in order to prevent another auto-prompt. + * @returns Whether to auto-prompt the user for biometric unlock + */ + async getShouldAutopromptNow(): Promise { + return this.shouldAutoPrompt; + } +} diff --git a/apps/desktop/src/key-management/biometrics/biometric.unix.main.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts similarity index 97% rename from apps/desktop/src/key-management/biometrics/biometric.unix.main.ts rename to apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts index f2bcf62e03e..791b4d6f885 100644 --- a/apps/desktop/src/key-management/biometrics/biometric.unix.main.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-linux.service.ts @@ -9,7 +9,7 @@ import { biometrics, passwords } from "@bitwarden/desktop-napi"; import { WindowMain } from "../../main/window.main"; import { isFlatpak, isLinux, isSnapStore } from "../../utils"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; const polkitPolicy = ` const policyFileName = "com.bitwarden.Bitwarden.policy"; const policyPath = "/usr/share/polkit-1/actions/"; -export default class BiometricUnixMain implements OsBiometricService { +export default class OsBiometricsServiceLinux implements OsBiometricService { constructor( private i18nservice: I18nService, private windowMain: WindowMain, diff --git a/apps/desktop/src/key-management/biometrics/biometric.darwin.main.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-mac.service.ts similarity index 92% rename from apps/desktop/src/key-management/biometrics/biometric.darwin.main.ts rename to apps/desktop/src/key-management/biometrics/os-biometrics-mac.service.ts index 0f26cc78fbf..e361084726a 100644 --- a/apps/desktop/src/key-management/biometrics/biometric.darwin.main.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-mac.service.ts @@ -3,9 +3,9 @@ import { systemPreferences } from "electron"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { passwords } from "@bitwarden/desktop-napi"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; -export default class BiometricDarwinMain implements OsBiometricService { +export default class OsBiometricsServiceMac implements OsBiometricService { constructor(private i18nservice: I18nService) {} async osSupportsBiometric(): Promise { diff --git a/apps/desktop/src/key-management/biometrics/biometric.windows.main.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts similarity index 93% rename from apps/desktop/src/key-management/biometrics/biometric.windows.main.ts rename to apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts index 0b0ad8c4500..9643c2b6f15 100644 --- a/apps/desktop/src/key-management/biometrics/biometric.windows.main.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts @@ -8,12 +8,12 @@ import { biometrics, passwords } from "@bitwarden/desktop-napi"; import { WindowMain } from "../../main/window.main"; -import { OsBiometricService } from "./desktop.biometrics.service"; +import { OsBiometricService } from "./os-biometrics.service"; const KEY_WITNESS_SUFFIX = "_witness"; const WITNESS_VALUE = "known key"; -export default class BiometricWindowsMain implements OsBiometricService { +export default class OsBiometricsServiceWindows implements OsBiometricService { // Use set helper method instead of direct access private _iv: string | null = null; // Use getKeyMaterial helper instead of direct access @@ -113,13 +113,19 @@ export default class BiometricWindowsMain implements OsBiometricService { this._iv = keyMaterial.ivB64; } - return { + const result = { key_material: { osKeyPartB64: this._osKeyHalf, clientKeyPartB64: clientKeyHalfB64, }, ivB64: this._iv, }; + + // napi-rs fails to convert null values + if (result.key_material.clientKeyPartB64 == null) { + delete result.key_material.clientKeyPartB64; + } + return result; } // Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey @@ -211,10 +217,17 @@ export default class BiometricWindowsMain implements OsBiometricService { clientKeyPartB64: string, ): biometrics.KeyMaterial { const key = symmetricKey?.macKeyB64 ?? symmetricKey?.keyB64; - return { + + const result = { osKeyPartB64: key, clientKeyPartB64, }; + + // napi-rs fails to convert null values + if (result.clientKeyPartB64 == null) { + delete result.clientKeyPartB64; + } + return result; } async osBiometricsNeedsSetup() { diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics.service.ts new file mode 100644 index 00000000000..f5132200149 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/os-biometrics.service.ts @@ -0,0 +1,32 @@ +export interface OsBiometricService { + osSupportsBiometric(): Promise; + /** + * Check whether support for biometric unlock requires setup. This can be automatic or manual. + * + * @returns true if biometrics support requires setup, false if it does not (is already setup, or did not require it in the first place) + */ + osBiometricsNeedsSetup: () => Promise; + /** + * Check whether biometrics can be automatically setup, or requires user interaction. + * + * @returns true if biometrics support can be automatically setup, false if it requires user interaction. + */ + osBiometricsCanAutoSetup: () => Promise; + /** + * Starts automatic biometric setup, which places the required configuration files / changes the required settings. + */ + osBiometricsSetup: () => Promise; + authenticateBiometric(): Promise; + getBiometricKey( + service: string, + key: string, + clientKeyHalfB64: string | undefined, + ): Promise; + setBiometricKey( + service: string, + key: string, + value: string, + clientKeyHalfB64: string | undefined, + ): Promise; + deleteBiometricKey(service: string, key: string): Promise; +} diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts new file mode 100644 index 00000000000..a08e68b53f2 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from "@angular/core"; + +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsStatus } from "@bitwarden/key-management"; + +import { DesktopBiometricsService } from "./desktop.biometrics.service"; + +/** + * This service implement the base biometrics service to provide desktop specific functions, + * specifically for the renderer process by passing messages to the main process. + */ +@Injectable() +export class RendererBiometricsService extends DesktopBiometricsService { + async authenticateWithBiometrics(): Promise { + return await ipc.keyManagement.biometric.authenticateWithBiometrics(); + } + + async getBiometricsStatus(): Promise { + return await ipc.keyManagement.biometric.getBiometricsStatus(); + } + + async unlockWithBiometricsForUser(userId: UserId): Promise { + return await ipc.keyManagement.biometric.unlockWithBiometricsForUser(userId); + } + + async getBiometricsStatusForUser(id: UserId): Promise { + return await ipc.keyManagement.biometric.getBiometricsStatusForUser(id); + } + + async setBiometricProtectedUnlockKeyForUser(userId: UserId, value: string): Promise { + return await ipc.keyManagement.biometric.setBiometricProtectedUnlockKeyForUser(userId, value); + } + + async deleteBiometricUnlockKeyForUser(userId: UserId): Promise { + return await ipc.keyManagement.biometric.deleteBiometricUnlockKeyForUser(userId); + } + + async setupBiometrics(): Promise { + return await ipc.keyManagement.biometric.setupBiometrics(); + } + + async setClientKeyHalfForUser(userId: UserId, value: string): Promise { + return await ipc.keyManagement.biometric.setClientKeyHalf(userId, value); + } + + async getShouldAutopromptNow(): Promise { + return await ipc.keyManagement.biometric.getShouldAutoprompt(); + } + + async setShouldAutopromptNow(value: boolean): Promise { + return await ipc.keyManagement.biometric.setShouldAutoprompt(value); + } +} diff --git a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts index 2d60cdeb663..2cc8d770f58 100644 --- a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts @@ -10,8 +10,8 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul import { DeviceType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; -import { BiometricsDisableReason, UnlockOptions } from "@bitwarden/key-management/angular"; +import { KeyService, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { UnlockOptions } from "@bitwarden/key-management/angular"; import { DesktopLockComponentService } from "./desktop-lock-component.service"; @@ -140,11 +140,7 @@ describe("DesktopLockComponentService", () => { describe("getAvailableUnlockOptions$", () => { interface MockInputs { hasMasterPassword: boolean; - osSupportsBiometric: boolean; - biometricLockSet: boolean; - biometricReady: boolean; - hasBiometricEncryptedUserKeyStored: boolean; - platformSupportsSecureStorage: boolean; + biometricsStatus: BiometricsStatus; pinDecryptionAvailable: boolean; } @@ -153,11 +149,7 @@ describe("DesktopLockComponentService", () => { // MP + PIN + Biometrics available { hasMasterPassword: true, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.Available, pinDecryptionAvailable: true, }, { @@ -169,7 +161,7 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], @@ -177,11 +169,7 @@ describe("DesktopLockComponentService", () => { // PIN + Biometrics available { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.Available, pinDecryptionAvailable: true, }, { @@ -193,43 +181,16 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: true, - disableReason: null, - }, - }, - ], - [ - // Biometrics available: user key stored with no secure storage - { - hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: false, - pinDecryptionAvailable: false, - }, - { - masterPassword: { - enabled: false, - }, - pin: { - enabled: false, - }, - biometrics: { - enabled: true, - disableReason: null, + biometricsStatus: BiometricsStatus.Available, }, }, ], [ // Biometrics available: no user key stored with no secure storage + // Biometric auth is available, but not unlock since there is no way to access the userkey { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: false, - biometricReady: true, - platformSupportsSecureStorage: false, + biometricsStatus: BiometricsStatus.NotEnabledLocally, pinDecryptionAvailable: false, }, { @@ -240,8 +201,8 @@ describe("DesktopLockComponentService", () => { enabled: false, }, biometrics: { - enabled: true, - disableReason: null, + enabled: false, + biometricsStatus: BiometricsStatus.NotEnabledLocally, }, }, ], @@ -249,11 +210,7 @@ describe("DesktopLockComponentService", () => { // Biometrics not available: biometric not ready { hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: false, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.HardwareUnavailable, pinDecryptionAvailable: false, }, { @@ -265,55 +222,7 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.SystemBiometricsUnavailable, - }, - }, - ], - [ - // Biometrics not available: biometric lock not set - { - hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: false, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, - pinDecryptionAvailable: false, - }, - { - masterPassword: { - enabled: false, - }, - pin: { - enabled: false, - }, - biometrics: { - enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, - }, - }, - ], - [ - // Biometrics not available: user key not stored - { - hasMasterPassword: false, - osSupportsBiometric: true, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: false, - biometricReady: true, - platformSupportsSecureStorage: true, - pinDecryptionAvailable: false, - }, - { - masterPassword: { - enabled: false, - }, - pin: { - enabled: false, - }, - biometrics: { - enabled: false, - disableReason: BiometricsDisableReason.EncryptedKeysUnavailable, + biometricsStatus: BiometricsStatus.HardwareUnavailable, }, }, ], @@ -321,11 +230,7 @@ describe("DesktopLockComponentService", () => { // Biometrics not available: OS doesn't support { hasMasterPassword: false, - osSupportsBiometric: false, - biometricLockSet: true, - hasBiometricEncryptedUserKeyStored: true, - biometricReady: true, - platformSupportsSecureStorage: true, + biometricsStatus: BiometricsStatus.PlatformUnsupported, pinDecryptionAvailable: false, }, { @@ -337,7 +242,7 @@ describe("DesktopLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: BiometricsDisableReason.NotSupportedOnOperatingSystem, + biometricsStatus: BiometricsStatus.PlatformUnsupported, }, }, ], @@ -355,13 +260,8 @@ describe("DesktopLockComponentService", () => { ); // Biometrics - biometricsService.supportsBiometric.mockResolvedValue(mockInputs.osSupportsBiometric); - vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(mockInputs.biometricLockSet); - keyService.hasUserKeyStored.mockResolvedValue(mockInputs.hasBiometricEncryptedUserKeyStored); - platformUtilsService.supportsSecureStorage.mockReturnValue( - mockInputs.platformSupportsSecureStorage, - ); - biometricEnabledMock.mockResolvedValue(mockInputs.biometricReady); + // TODO: FIXME + biometricsService.getBiometricsStatusForUser.mockResolvedValue(mockInputs.biometricsStatus); // PIN pinService.isPinDecryptionAvailable.mockResolvedValue(mockInputs.pinDecryptionAvailable); diff --git a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts index 76232fd3196..1d2d68c1d97 100644 --- a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts @@ -5,25 +5,17 @@ import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { DeviceType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService } from "@bitwarden/key-management"; -import { - BiometricsDisableReason, - LockComponentService, - UnlockOptions, -} from "@bitwarden/key-management/angular"; +import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; export class DesktopLockComponentService implements LockComponentService { private readonly userDecryptionOptionsService = inject(UserDecryptionOptionsServiceAbstraction); private readonly platformUtilsService = inject(PlatformUtilsService); private readonly biometricsService = inject(BiometricsService); private readonly pinService = inject(PinServiceAbstraction); - private readonly vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService); - private readonly keyService = inject(KeyService); constructor() {} @@ -52,77 +44,29 @@ export class DesktopLockComponentService implements LockComponentService { } } - private async isBiometricLockSet(userId: UserId): Promise { - const biometricLockSet = await this.vaultTimeoutSettingsService.isBiometricLockSet(userId); - const hasBiometricEncryptedUserKeyStored = await this.keyService.hasUserKeyStored( - KeySuffixOptions.Biometric, - userId, - ); - const platformSupportsSecureStorage = this.platformUtilsService.supportsSecureStorage(); - - return ( - biometricLockSet && (hasBiometricEncryptedUserKeyStored || !platformSupportsSecureStorage) - ); - } - - private async isBiometricsSupportedAndReady( - userId: UserId, - ): Promise<{ supportsBiometric: boolean; biometricReady: boolean }> { - const supportsBiometric = await this.biometricsService.supportsBiometric(); - const biometricReady = await ipc.keyManagement.biometric.enabled(userId); - return { supportsBiometric, biometricReady }; - } - getAvailableUnlockOptions$(userId: UserId): Observable { return combineLatest([ // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to - defer(() => this.isBiometricsSupportedAndReady(userId)), - defer(() => this.isBiometricLockSet(userId)), + defer(() => this.biometricsService.getBiometricsStatusForUser(userId)), this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), defer(() => this.pinService.isPinDecryptionAvailable(userId)), ]).pipe( - map( - ([biometricsData, isBiometricsLockSet, userDecryptionOptions, pinDecryptionAvailable]) => { - const disableReason = this.getBiometricsDisabledReason( - biometricsData.supportsBiometric, - isBiometricsLockSet, - biometricsData.biometricReady, - ); + map(([biometricsStatus, userDecryptionOptions, pinDecryptionAvailable]) => { + const unlockOpts: UnlockOptions = { + masterPassword: { + enabled: userDecryptionOptions.hasMasterPassword, + }, + pin: { + enabled: pinDecryptionAvailable, + }, + biometrics: { + enabled: biometricsStatus == BiometricsStatus.Available, + biometricsStatus: biometricsStatus, + }, + }; - const unlockOpts: UnlockOptions = { - masterPassword: { - enabled: userDecryptionOptions.hasMasterPassword, - }, - pin: { - enabled: pinDecryptionAvailable, - }, - biometrics: { - enabled: - biometricsData.supportsBiometric && - isBiometricsLockSet && - biometricsData.biometricReady, - disableReason: disableReason, - }, - }; - - return unlockOpts; - }, - ), + return unlockOpts; + }), ); } - - private getBiometricsDisabledReason( - osSupportsBiometric: boolean, - biometricLockSet: boolean, - biometricReady: boolean, - ): BiometricsDisableReason | null { - if (!osSupportsBiometric) { - return BiometricsDisableReason.NotSupportedOnOperatingSystem; - } else if (!biometricLockSet) { - return BiometricsDisableReason.EncryptedKeysUnavailable; - } else if (!biometricReady) { - return BiometricsDisableReason.SystemBiometricsUnavailable; - } - return null; - } } diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index ffb6159a46f..b73542ca725 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -1,36 +1,58 @@ import { ipcRenderer } from "electron"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsStatus } from "@bitwarden/key-management"; import { BiometricMessage, BiometricAction } from "../types/biometric-message"; const biometric = { - enabled: (userId: string): Promise => + authenticateWithBiometrics: (): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.EnabledForUser, - key: `${userId}_user_biometric`, - keySuffix: KeySuffixOptions.Biometric, + action: BiometricAction.Authenticate, + } satisfies BiometricMessage), + getBiometricsStatus: (): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.GetStatus, + } satisfies BiometricMessage), + unlockWithBiometricsForUser: (userId: string): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.UnlockForUser, userId: userId, } satisfies BiometricMessage), - osSupported: (): Promise => + getBiometricsStatusForUser: (userId: string): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.OsSupported, + action: BiometricAction.GetStatusForUser, + userId: userId, } satisfies BiometricMessage), - biometricsNeedsSetup: (): Promise => + setBiometricProtectedUnlockKeyForUser: (userId: string, value: string): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.NeedsSetup, + action: BiometricAction.SetKeyForUser, + userId: userId, + key: value, } satisfies BiometricMessage), - biometricsSetup: (): Promise => + deleteBiometricUnlockKeyForUser: (userId: string): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.RemoveKeyForUser, + userId: userId, + } satisfies BiometricMessage), + setupBiometrics: (): Promise => ipcRenderer.invoke("biometric", { action: BiometricAction.Setup, } satisfies BiometricMessage), - biometricsCanAutoSetup: (): Promise => + setClientKeyHalf: (userId: string, value: string): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.CanAutoSetup, + action: BiometricAction.SetClientKeyHalf, + userId: userId, + key: value, } satisfies BiometricMessage), - authenticate: (): Promise => + getShouldAutoprompt: (): Promise => ipcRenderer.invoke("biometric", { - action: BiometricAction.Authenticate, + action: BiometricAction.GetShouldAutoprompt, + } satisfies BiometricMessage), + setShouldAutoprompt: (should: boolean): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.SetShouldAutoprompt, + data: should, } satisfies BiometricMessage), }; diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 323d0cd3f7b..9ab15230604 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -3362,6 +3362,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index a4842249c93..3232eef2b9b 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -28,8 +28,9 @@ import { DefaultBiometricStateService } from "@bitwarden/key-management"; /* eslint-enable import/no-restricted-paths */ import { DesktopAutofillSettingsService } from "./autofill/services/desktop-autofill-settings.service"; -import { BiometricsRendererIPCListener } from "./key-management/biometrics/biometric.renderer-ipc.listener"; -import { BiometricsService, DesktopBiometricsService } from "./key-management/biometrics/index"; +import { DesktopBiometricsService } from "./key-management/biometrics/desktop.biometrics.service"; +import { MainBiometricsIPCListener } from "./key-management/biometrics/main-biometrics-ipc.listener"; +import { MainBiometricsService } from "./key-management/biometrics/main-biometrics.service"; import { MenuMain } from "./main/menu/menu.main"; import { MessagingMain } from "./main/messaging.main"; import { NativeMessagingMain } from "./main/native-messaging.main"; @@ -61,7 +62,7 @@ export class Main { messagingService: MessageSender; environmentService: DefaultEnvironmentService; desktopCredentialStorageListener: DesktopCredentialStorageListener; - biometricsRendererIPCListener: BiometricsRendererIPCListener; + mainBiometricsIpcListener: MainBiometricsIPCListener; desktopSettingsService: DesktopSettingsService; mainCryptoFunctionService: MainCryptoFunctionService; migrationRunner: MigrationRunner; @@ -177,6 +178,15 @@ export class Main { this.desktopSettingsService = new DesktopSettingsService(stateProvider); const biometricStateService = new DefaultBiometricStateService(stateProvider); + this.biometricsService = new MainBiometricsService( + this.i18nService, + this.windowMain, + this.logService, + this.messagingService, + process.platform, + biometricStateService, + ); + this.windowMain = new WindowMain( biometricStateService, this.logService, @@ -187,7 +197,6 @@ export class Main { ); this.messagingMain = new MessagingMain(this, this.desktopSettingsService); this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain); - this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.desktopSettingsService); const messageSubject = new Subject>>(); this.messagingService = MessageSender.combine( @@ -218,22 +227,19 @@ export class Main { this.versionMain, ); - this.biometricsService = new BiometricsService( - this.i18nService, + this.trayMain = new TrayMain( this.windowMain, - this.logService, - this.messagingService, - process.platform, + this.i18nService, + this.desktopSettingsService, biometricStateService, + this.biometricsService, ); this.desktopCredentialStorageListener = new DesktopCredentialStorageListener( "Bitwarden", - this.biometricsService, this.logService, ); - this.biometricsRendererIPCListener = new BiometricsRendererIPCListener( - "Bitwarden", + this.mainBiometricsIpcListener = new MainBiometricsIPCListener( this.biometricsService, this.logService, ); @@ -267,7 +273,7 @@ export class Main { bootstrap() { this.desktopCredentialStorageListener.init(); - this.biometricsRendererIPCListener.init(); + this.mainBiometricsIpcListener.init(); // Run migrations first, then other things this.migrationRunner.run().then( async () => { diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts index 52a8615a1da..9fa7fe6143f 100644 --- a/apps/desktop/src/main/tray.main.ts +++ b/apps/desktop/src/main/tray.main.ts @@ -6,6 +6,7 @@ import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray import { firstValueFrom } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { BiometricStateService, BiometricsService } from "@bitwarden/key-management"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; @@ -23,6 +24,8 @@ export class TrayMain { private windowMain: WindowMain, private i18nService: I18nService, private desktopSettingsService: DesktopSettingsService, + private biometricsStateService: BiometricStateService, + private biometricService: BiometricsService, ) { if (process.platform === "win32") { this.icon = path.join(__dirname, "/images/icon.ico"); @@ -72,6 +75,10 @@ export class TrayMain { } }); + win.on("restore", async () => { + await this.biometricService.setShouldAutopromptNow(true); + }); + win.on("close", async (e: Event) => { if (await firstValueFrom(this.desktopSettingsService.closeToTray$)) { if (!this.windowMain.isQuitting) { diff --git a/apps/desktop/src/models/native-messaging/legacy-message.ts b/apps/desktop/src/models/native-messaging/legacy-message.ts index a2bcf2aa7e5..99047cdcd34 100644 --- a/apps/desktop/src/models/native-messaging/legacy-message.ts +++ b/apps/desktop/src/models/native-messaging/legacy-message.ts @@ -1,5 +1,6 @@ export type LegacyMessage = { command: string; + messageId: number; userId?: string; timestamp?: number; diff --git a/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts b/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts index 294f9a3cbe9..ca4d9a2d3ca 100644 --- a/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts +++ b/apps/desktop/src/platform/main/desktop-credential-storage-listener.ts @@ -2,18 +2,12 @@ // @ts-strict-ignore import { ipcMain } from "electron"; -import { BiometricKey } from "@bitwarden/common/auth/types/biometric-key"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { passwords } from "@bitwarden/desktop-napi"; -import { DesktopBiometricsService } from "../../key-management/biometrics/index"; - -const AuthRequiredSuffix = "_biometric"; - export class DesktopCredentialStorageListener { constructor( private serviceName: string, - private biometricService: DesktopBiometricsService, private logService: ConsoleLogService, ) {} @@ -54,13 +48,7 @@ export class DesktopCredentialStorageListener { // Gracefully handle old keytar values, and if detected updated the entry to the proper format private async getPassword(serviceName: string, key: string, keySuffix: string) { - let val: string; - // todo: remove this when biometrics has been migrated to desktop_native - if (keySuffix === AuthRequiredSuffix) { - val = (await this.biometricService.getBiometricKey(serviceName, key)) ?? null; - } else { - val = await passwords.getPassword(serviceName, key); - } + const val = await passwords.getPassword(serviceName, key); try { JSON.parse(val); @@ -72,25 +60,10 @@ export class DesktopCredentialStorageListener { } private async setPassword(serviceName: string, key: string, value: string, keySuffix: string) { - if (keySuffix === AuthRequiredSuffix) { - const valueObj = JSON.parse(value) as BiometricKey; - await this.biometricService.setEncryptionKeyHalf({ - service: serviceName, - key, - value: valueObj?.clientEncKeyHalf, - }); - // Value is usually a JSON string, but we need to pass the key half as well, so we re-stringify key here. - await this.biometricService.setBiometricKey(serviceName, key, JSON.stringify(valueObj?.key)); - } else { - await passwords.setPassword(serviceName, key, value); - } + await passwords.setPassword(serviceName, key, value); } private async deletePassword(serviceName: string, key: string, keySuffix: string) { - if (keySuffix === AuthRequiredSuffix) { - await this.biometricService.deleteBiometricKey(serviceName, key); - } else { - await passwords.deletePassword(serviceName, key); - } + await passwords.deletePassword(serviceName, key); } } diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 0b61d894776..9c1986fb61d 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -87,6 +87,7 @@ const nativeMessaging = { }, sendMessage: (message: { appId: string; + messageId?: number; command?: string; sharedSecret?: string; message?: EncString; diff --git a/apps/desktop/src/platform/services/electron-key.service.spec.ts b/apps/desktop/src/platform/services/electron-key.service.spec.ts deleted file mode 100644 index fc87ae4ceaf..00000000000 --- a/apps/desktop/src/platform/services/electron-key.service.spec.ts +++ /dev/null @@ -1,115 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; -import { mock } from "jest-mock-extended"; - -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { makeEncString } from "@bitwarden/common/spec"; -import { CsprngArray } from "@bitwarden/common/types/csprng"; -import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey } from "@bitwarden/common/types/key"; -import { KdfConfigService, BiometricStateService } from "@bitwarden/key-management"; - -import { - FakeAccountService, - mockAccountServiceWith, -} from "../../../../../libs/common/spec/fake-account-service"; - -import { ElectronKeyService } from "./electron-key.service"; - -describe("electronKeyService", () => { - let sut: ElectronKeyService; - - const pinService = mock(); - const keyGenerationService = mock(); - const cryptoFunctionService = mock(); - const encryptService = mock(); - const platformUtilService = mock(); - const logService = mock(); - const stateService = mock(); - let masterPasswordService: FakeMasterPasswordService; - let accountService: FakeAccountService; - let stateProvider: FakeStateProvider; - const biometricStateService = mock(); - const kdfConfigService = mock(); - - const mockUserId = "mock user id" as UserId; - - beforeEach(() => { - accountService = mockAccountServiceWith("userId" as UserId); - masterPasswordService = new FakeMasterPasswordService(); - stateProvider = new FakeStateProvider(accountService); - - sut = new ElectronKeyService( - pinService, - masterPasswordService, - keyGenerationService, - cryptoFunctionService, - encryptService, - platformUtilService, - logService, - stateService, - accountService, - stateProvider, - biometricStateService, - kdfConfigService, - ); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("setUserKey", () => { - let mockUserKey: UserKey; - - beforeEach(() => { - const mockRandomBytes = new Uint8Array(64) as CsprngArray; - mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; - }); - - describe("Biometric Key refresh", () => { - const encClientKeyHalf = makeEncString(); - const decClientKeyHalf = "decrypted client key half"; - - beforeEach(() => { - encClientKeyHalf.decrypt = jest.fn().mockResolvedValue(decClientKeyHalf); - }); - - it("sets a Biometric key if getBiometricUnlock is true and the platform supports secure storage", async () => { - biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); - platformUtilService.supportsSecureStorage.mockReturnValue(true); - biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true); - biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue(encClientKeyHalf); - - await sut.setUserKey(mockUserKey, mockUserId); - - expect(stateService.setUserKeyBiometric).toHaveBeenCalledWith( - expect.objectContaining({ key: expect.any(String), clientEncKeyHalf: decClientKeyHalf }), - { - userId: mockUserId, - }, - ); - }); - - it("clears the Biometric key if getBiometricUnlock is false or the platform does not support secure storage", async () => { - biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); - platformUtilService.supportsSecureStorage.mockReturnValue(false); - - await sut.setUserKey(mockUserKey, mockUserId); - - expect(stateService.setUserKeyBiometric).toHaveBeenCalledWith(null, { - userId: mockUserId, - }); - }); - }); - }); -}); diff --git a/apps/desktop/src/platform/services/electron-key.service.ts b/apps/desktop/src/platform/services/electron-key.service.ts index a4719873375..9a18753e4b5 100644 --- a/apps/desktop/src/platform/services/electron-key.service.ts +++ b/apps/desktop/src/platform/services/electron-key.service.ts @@ -1,7 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; - import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; @@ -13,7 +11,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CsprngString } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; @@ -24,6 +21,8 @@ import { BiometricStateService, } from "@bitwarden/key-management"; +import { DesktopBiometricsService } from "src/key-management/biometrics/desktop.biometrics.service"; + export class ElectronKeyService extends DefaultKeyService { constructor( pinService: PinServiceAbstraction, @@ -38,6 +37,7 @@ export class ElectronKeyService extends DefaultKeyService { stateProvider: StateProvider, private biometricStateService: BiometricStateService, kdfConfigService: KdfConfigService, + private biometricService: DesktopBiometricsService, ) { super( pinService, @@ -55,19 +55,10 @@ export class ElectronKeyService extends DefaultKeyService { } override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - return await this.stateService.hasUserKeyBiometric({ userId: userId }); - } return super.hasUserKeyStored(keySuffix, userId); } override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - await this.stateService.setUserKeyBiometric(null, { userId: userId }); - await this.biometricStateService.removeEncryptedClientKeyHalf(userId); - await this.clearDeprecatedKeys(KeySuffixOptions.Biometric, userId); - return; - } // 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 await super.clearStoredUserKey(keySuffix, userId); @@ -76,52 +67,35 @@ export class ElectronKeyService extends DefaultKeyService { protected override async storeAdditionalKeys(key: UserKey, userId: UserId) { await super.storeAdditionalKeys(key, userId); - const storeBiometricKey = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId); - - if (storeBiometricKey) { - await this.storeBiometricKey(key, userId); - } else { - await this.stateService.setUserKeyBiometric(null, { userId: userId }); + if (await this.biometricStateService.getBiometricUnlockEnabled(userId)) { + await this.storeBiometricsProtectedUserKey(key, userId); } - await this.clearDeprecatedKeys(KeySuffixOptions.Biometric, userId); } protected override async getKeyFromStorage( keySuffix: KeySuffixOptions, userId?: UserId, ): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const userKey = await this.stateService.getUserKeyBiometric({ userId: userId }); - return userKey == null - ? null - : (new SymmetricCryptoKey(Utils.fromB64ToArray(userKey)) as UserKey); - } return await super.getKeyFromStorage(keySuffix, userId); } - protected async storeBiometricKey(key: UserKey, userId?: UserId): Promise { + protected async storeBiometricsProtectedUserKey( + userKey: UserKey, + userId?: UserId, + ): Promise { // May resolve to null, in which case no client key have is required - const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(key, userId); - await this.stateService.setUserKeyBiometric( - { key: key.keyB64, clientEncKeyHalf }, - { userId: userId }, - ); + // TODO: Move to windows implementation + const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId); + await this.biometricService.setClientKeyHalfForUser(userId, clientEncKeyHalf); + await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64); } protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise { - if (keySuffix === KeySuffixOptions.Biometric) { - const biometricUnlockPromise = - userId == null - ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) - : this.biometricStateService.getBiometricUnlockEnabled(userId); - const biometricUnlock = await biometricUnlockPromise; - return biometricUnlock && this.platformUtilService.supportsSecureStorage(); - } return await super.shouldStoreKey(keySuffix, userId); } protected override async clearAllStoredUserKeys(userId?: UserId): Promise { - await this.clearStoredUserKey(KeySuffixOptions.Biometric, userId); + await this.biometricService.deleteBiometricUnlockKeyForUser(userId); await super.clearAllStoredUserKeys(userId); } @@ -135,18 +109,18 @@ export class ElectronKeyService extends DefaultKeyService { } // Retrieve existing key half if it exists - let biometricKey = await this.biometricStateService + let clientKeyHalf = await this.biometricStateService .getEncryptedClientKeyHalf(userId) .then((result) => result?.decrypt(null /* user encrypted */, userKey)) .then((result) => result as CsprngString); - if (biometricKey == null && userKey != null) { + if (clientKeyHalf == null && userKey != null) { // Set a key half if it doesn't exist const keyBytes = await this.cryptoFunctionService.randomBytes(32); - biometricKey = Utils.fromBufferToUtf8(keyBytes) as CsprngString; - const encKey = await this.encryptService.encrypt(biometricKey, userKey); + clientKeyHalf = Utils.fromBufferToUtf8(keyBytes) as CsprngString; + const encKey = await this.encryptService.encrypt(clientKeyHalf, userKey); await this.biometricStateService.setEncryptedClientKeyHalf(encKey, userId); } - return biometricKey; + return clientKeyHalf; } } diff --git a/apps/desktop/src/services/biometric-message-handler.service.spec.ts b/apps/desktop/src/services/biometric-message-handler.service.spec.ts new file mode 100644 index 00000000000..13b668f6b83 --- /dev/null +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -0,0 +1,123 @@ +import { NgZone } from "@angular/core"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { FakeAccountService } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; +import { KeyService, BiometricsService, BiometricStateService } from "@bitwarden/key-management"; + +import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; + +import { BiometricMessageHandlerService } from "./biometric-message-handler.service"; + +(global as any).ipc = { + platform: { + reloadProcess: jest.fn(), + }, +}; + +const SomeUser = "SomeUser" as UserId; +const AnotherUser = "SomeOtherUser" as UserId; +const accounts = { + [SomeUser]: { + name: "some user", + email: "some.user@example.com", + emailVerified: true, + }, + [AnotherUser]: { + name: "some other user", + email: "some.other.user@example.com", + emailVerified: true, + }, +}; + +describe("BiometricMessageHandlerService", () => { + let service: BiometricMessageHandlerService; + + let cryptoFunctionService: MockProxy; + let keyService: MockProxy; + let encryptService: MockProxy; + let logService: MockProxy; + let messagingService: MockProxy; + let desktopSettingsService: DesktopSettingsService; + let biometricStateService: BiometricStateService; + let biometricsService: MockProxy; + let dialogService: MockProxy; + let accountService: AccountService; + let authService: MockProxy; + let ngZone: MockProxy; + + beforeEach(() => { + cryptoFunctionService = mock(); + keyService = mock(); + encryptService = mock(); + logService = mock(); + messagingService = mock(); + desktopSettingsService = mock(); + biometricStateService = mock(); + biometricsService = mock(); + dialogService = mock(); + + accountService = new FakeAccountService(accounts); + authService = mock(); + ngZone = mock(); + + service = new BiometricMessageHandlerService( + cryptoFunctionService, + keyService, + encryptService, + logService, + messagingService, + desktopSettingsService, + biometricStateService, + biometricsService, + dialogService, + accountService, + authService, + ngZone, + ); + }); + + describe("process reload", () => { + const testCases = [ + // don't reload when the active user is the requested one and unlocked + [SomeUser, AuthenticationStatus.Unlocked, SomeUser, false, false], + // do reload when the active user is the requested one but locked + [SomeUser, AuthenticationStatus.Locked, SomeUser, false, true], + // always reload when another user is active than the requested one + [SomeUser, AuthenticationStatus.Unlocked, AnotherUser, false, true], + [SomeUser, AuthenticationStatus.Locked, AnotherUser, false, true], + + // don't reload in dev mode + [SomeUser, AuthenticationStatus.Unlocked, SomeUser, true, false], + [SomeUser, AuthenticationStatus.Locked, SomeUser, true, false], + [SomeUser, AuthenticationStatus.Unlocked, AnotherUser, true, false], + [SomeUser, AuthenticationStatus.Locked, AnotherUser, true, false], + ]; + + it.each(testCases)( + "process reload for active user %s with auth status %s and other user %s and isdev: %s should process reload: %s", + async (activeUser, authStatus, messageUser, isDev, shouldReload) => { + await accountService.switchAccount(activeUser as UserId); + authService.authStatusFor$.mockReturnValue(of(authStatus as AuthenticationStatus)); + (global as any).ipc.platform.isDev = isDev; + (global as any).ipc.platform.reloadProcess.mockClear(); + await service.processReloadWhenRequired(messageUser as UserId); + + if (shouldReload) { + expect((global as any).ipc.platform.reloadProcess).toHaveBeenCalled(); + } else { + expect((global as any).ipc.platform.reloadProcess).not.toHaveBeenCalled(); + } + }, + ); + }); +}); diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 68b2e8f505c..ea1e7e76c56 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -10,13 +10,18 @@ import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/c import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; -import { BiometricStateService, BiometricsService, KeyService } from "@bitwarden/key-management"; +import { + BiometricStateService, + BiometricsCommands, + BiometricsService, + BiometricsStatus, + KeyService, +} from "@bitwarden/key-management"; import { BrowserSyncVerificationDialogComponent } from "../app/components/browser-sync-verification-dialog.component"; import { LegacyMessage } from "../models/native-messaging/legacy-message"; @@ -54,6 +59,9 @@ export class BiometricMessageHandlerService { const accounts = await firstValueFrom(this.accountService.accounts$); const userIds = Object.keys(accounts); if (!userIds.includes(rawMessage.userId)) { + this.logService.info( + "[Native Messaging IPC] Received message for user that is not logged into the desktop app.", + ); ipc.platform.nativeMessaging.sendMessage({ command: "wrongUserId", appId: appId, @@ -62,6 +70,7 @@ export class BiometricMessageHandlerService { } if (await firstValueFrom(this.desktopSettingService.browserIntegrationFingerprintEnabled$)) { + this.logService.info("[Native Messaging IPC] Requesting fingerprint verification."); ipc.platform.nativeMessaging.sendMessage({ command: "verifyFingerprint", appId: appId, @@ -81,6 +90,7 @@ export class BiometricMessageHandlerService { const browserSyncVerified = await firstValueFrom(dialogRef.closed); if (browserSyncVerified !== true) { + this.logService.info("[Native Messaging IPC] Fingerprint verification failed."); return; } } @@ -90,6 +100,9 @@ export class BiometricMessageHandlerService { } if ((await ipc.platform.ephemeralStore.getEphemeralValue(appId)) == null) { + this.logService.info( + "[Native Messaging IPC] Epheremal secret for secure channel is missing. Invalidating encryption...", + ); ipc.platform.nativeMessaging.sendMessage({ command: "invalidateEncryption", appId: appId, @@ -106,6 +119,9 @@ export class BiometricMessageHandlerService { // Shared secret is invalidated, force re-authentication if (message == null) { + this.logService.info( + "[Native Messaging IPC] Secure channel failed to decrypt message. Invalidating encryption...", + ); ipc.platform.nativeMessaging.sendMessage({ command: "invalidateEncryption", appId: appId, @@ -114,20 +130,86 @@ export class BiometricMessageHandlerService { } if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { - this.logService.error("NativeMessage is to old, ignoring."); + this.logService.info("[Native Messaging IPC] Received a too old message. Ignoring."); return; } + const messageId = message.messageId; + switch (message.command) { - case "biometricUnlock": { + case BiometricsCommands.UnlockWithBiometricsForUser: { + await this.handleUnlockWithBiometricsForUser(message, messageId, appId); + break; + } + case BiometricsCommands.AuthenticateWithBiometrics: { + try { + const unlocked = await this.biometricsService.authenticateWithBiometrics(); + await this.send( + { + command: BiometricsCommands.AuthenticateWithBiometrics, + messageId, + response: unlocked, + }, + appId, + ); + } catch (e) { + this.logService.error("[Native Messaging IPC] Biometric authentication failed", e); + await this.send( + { command: BiometricsCommands.AuthenticateWithBiometrics, messageId, response: false }, + appId, + ); + } + break; + } + case BiometricsCommands.GetBiometricsStatus: { + const status = await this.biometricsService.getBiometricsStatus(); + return this.send( + { + command: BiometricsCommands.GetBiometricsStatus, + messageId, + response: status, + }, + appId, + ); + } + case BiometricsCommands.GetBiometricsStatusForUser: { + let status = await this.biometricsService.getBiometricsStatusForUser( + message.userId as UserId, + ); + if (status == BiometricsStatus.NotEnabledLocally) { + status = BiometricsStatus.NotEnabledInConnectedDesktopApp; + } + return this.send( + { + command: BiometricsCommands.GetBiometricsStatusForUser, + messageId, + response: status, + }, + appId, + ); + } + // TODO: legacy, remove after 2025.01 + case BiometricsCommands.IsAvailable: { + const available = + (await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available; + return this.send( + { + command: BiometricsCommands.IsAvailable, + response: available ? "available" : "not available", + }, + appId, + ); + } + // TODO: legacy, remove after 2025.01 + case BiometricsCommands.Unlock: { const isTemporarilyDisabled = (await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId)) && - !(await this.biometricsService.supportsBiometric()); + !((await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available); if (isTemporarilyDisabled) { return this.send({ command: "biometricUnlock", response: "not available" }, appId); } - if (!(await this.biometricsService.supportsBiometric())) { + if (!((await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available)) { return this.send({ command: "biometricUnlock", response: "not supported" }, appId); } @@ -158,10 +240,7 @@ export class BiometricMessageHandlerService { } try { - const userKey = await this.keyService.getUserKeyFromStorage( - KeySuffixOptions.Biometric, - message.userId, - ); + const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); if (userKey != null) { await this.send( @@ -189,19 +268,8 @@ export class BiometricMessageHandlerService { } catch (e) { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } - break; } - case "biometricUnlockAvailable": { - const isAvailable = await this.biometricsService.supportsBiometric(); - return this.send( - { - command: "biometricUnlockAvailable", - response: isAvailable ? "available" : "not available", - }, - appId, - ); - } default: this.logService.error("NativeMessage, got unknown command: " + message.command); break; @@ -216,7 +284,11 @@ export class BiometricMessageHandlerService { SymmetricCryptoKey.fromString(await ipc.platform.ephemeralStore.getEphemeralValue(appId)), ); - ipc.platform.nativeMessaging.sendMessage({ appId: appId, message: encrypted }); + ipc.platform.nativeMessaging.sendMessage({ + appId: appId, + messageId: message.messageId, + message: encrypted, + }); } private async secureCommunication(remotePublicKey: Uint8Array, appId: string) { @@ -226,6 +298,7 @@ export class BiometricMessageHandlerService { new SymmetricCryptoKey(secret).keyB64, ); + this.logService.info("[Native Messaging IPC] Setting up secure channel"); const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt( secret, remotePublicKey, @@ -234,7 +307,62 @@ export class BiometricMessageHandlerService { ipc.platform.nativeMessaging.sendMessage({ appId: appId, command: "setupEncryption", + messageId: -1, // to indicate to the other side that this is a new desktop client. refactor later to use proper versioning sharedSecret: Utils.fromBufferToB64(encryptedSecret), }); } + + private async handleUnlockWithBiometricsForUser( + message: LegacyMessage, + messageId: number, + appId: string, + ) { + const messageUserId = message.userId as UserId; + try { + const userKey = await this.biometricsService.unlockWithBiometricsForUser(messageUserId); + if (userKey != null) { + this.logService.info("[Native Messaging IPC] Biometric unlock for user: " + messageUserId); + await this.send( + { + command: BiometricsCommands.UnlockWithBiometricsForUser, + response: true, + messageId, + userKeyB64: userKey.keyB64, + }, + appId, + ); + await this.processReloadWhenRequired(messageUserId); + } else { + await this.send( + { + command: BiometricsCommands.UnlockWithBiometricsForUser, + messageId, + response: false, + }, + appId, + ); + } + } catch (e) { + await this.send( + { command: BiometricsCommands.UnlockWithBiometricsForUser, messageId, response: false }, + appId, + ); + } + } + + /** A process reload after a biometric unlock should happen if the userkey that was used for biometric unlock is for a different user than the + * currently active account. The userkey for the active account was in memory anyways. Further, if the desktop app is locked, a reload should occur (since the userkey was not already in memory). + */ + async processReloadWhenRequired(messageUserId: UserId) { + const currentlyActiveAccountId = (await firstValueFrom(this.accountService.activeAccount$)).id; + const isCurrentlyActiveAccountUnlocked = + (await firstValueFrom(this.authService.authStatusFor$(currentlyActiveAccountId))) == + AuthenticationStatus.Unlocked; + + if (currentlyActiveAccountId !== messageUserId || !isCurrentlyActiveAccountUnlocked) { + if (!ipc.platform.isDev) { + ipc.platform.reloadProcess(); + } + } + } } diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index 0db7b60a2df..7946280e9a6 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -1,15 +1,23 @@ export enum BiometricAction { - EnabledForUser = "enabled", - OsSupported = "osSupported", Authenticate = "authenticate", - NeedsSetup = "needsSetup", + GetStatus = "status", + + UnlockForUser = "unlockForUser", + GetStatusForUser = "statusForUser", + SetKeyForUser = "setKeyForUser", + RemoveKeyForUser = "removeKeyForUser", + + SetClientKeyHalf = "setClientKeyHalf", + Setup = "setup", - CanAutoSetup = "canAutoSetup", + + GetShouldAutoprompt = "getShouldAutoprompt", + SetShouldAutoprompt = "setShouldAutoprompt", } export type BiometricMessage = { action: BiometricAction; - keySuffix?: string; key?: string; userId?: string; + data?: any; }; diff --git a/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts index 5eb26a8c76c..3c941fe24c7 100644 --- a/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts @@ -4,6 +4,7 @@ import { firstValueFrom, of } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; +import { BiometricsStatus } from "@bitwarden/key-management"; import { WebLockComponentService } from "./web-lock-component.service"; @@ -86,7 +87,7 @@ describe("WebLockComponentService", () => { }, biometrics: { enabled: false, - disableReason: null, + biometricsStatus: BiometricsStatus.PlatformUnsupported, }, }); }); diff --git a/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts index dc124983c9a..02910966d6e 100644 --- a/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts @@ -6,6 +6,7 @@ import { UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; +import { BiometricsStatus } from "@bitwarden/key-management"; import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; export class WebLockComponentService implements LockComponentService { @@ -45,7 +46,7 @@ export class WebLockComponentService implements LockComponentService { }, biometrics: { enabled: false, - disableReason: null, + biometricsStatus: BiometricsStatus.PlatformUnsupported, }, }; return unlockOpts; diff --git a/apps/web/src/app/key-management/web-biometric.service.ts b/apps/web/src/app/key-management/web-biometric.service.ts index 4681eb6fa49..0c58c0da759 100644 --- a/apps/web/src/app/key-management/web-biometric.service.ts +++ b/apps/web/src/app/key-management/web-biometric.service.ts @@ -1,27 +1,27 @@ -import { BiometricsService } from "@bitwarden/key-management"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; export class WebBiometricsService extends BiometricsService { - async supportsBiometric(): Promise { + async authenticateWithBiometrics(): Promise { return false; } - async isBiometricUnlockAvailable(): Promise { + async getBiometricsStatus(): Promise { + return BiometricsStatus.PlatformUnsupported; + } + + async unlockWithBiometricsForUser(userId: UserId): Promise { + return null; + } + + async getBiometricsStatusForUser(userId: UserId): Promise { + return BiometricsStatus.PlatformUnsupported; + } + + async getShouldAutopromptNow(): Promise { return false; } - async authenticateBiometric(): Promise { - throw new Error("Method not implemented."); - } - - async biometricsNeedsSetup(): Promise { - throw new Error("Method not implemented."); - } - - async biometricsSupportsAutoSetup(): Promise { - throw new Error("Method not implemented."); - } - - async biometricsSetup(): Promise { - throw new Error("Method not implemented."); - } + async setShouldAutopromptNow(value: boolean): Promise {} } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index d990a7315f2..f5940b8e144 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -279,12 +279,13 @@ import { ImportServiceAbstraction, } from "@bitwarden/importer/core"; import { - KeyService as KeyServiceAbstraction, - DefaultKeyService as KeyService, + KeyService, + DefaultKeyService, BiometricStateService, DefaultBiometricStateService, - KdfConfigService, + BiometricsService, DefaultKdfConfigService, + KdfConfigService, UserAsymmetricKeysRegenerationService, DefaultUserAsymmetricKeysRegenerationService, UserAsymmetricKeysRegenerationApiService, @@ -416,7 +417,7 @@ const safeProviders: SafeProvider[] = [ deps: [ AccountServiceAbstraction, MessagingServiceAbstraction, - KeyServiceAbstraction, + KeyService, ApiServiceAbstraction, StateServiceAbstraction, TokenServiceAbstraction, @@ -428,7 +429,7 @@ const safeProviders: SafeProvider[] = [ deps: [ AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, - KeyServiceAbstraction, + KeyService, ApiServiceAbstraction, TokenServiceAbstraction, AppIdServiceAbstraction, @@ -471,7 +472,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: CipherServiceAbstraction, useFactory: ( - keyService: KeyServiceAbstraction, + keyService: KeyService, domainSettingsService: DomainSettingsService, apiService: ApiServiceAbstraction, i18nService: I18nServiceAbstraction, @@ -501,7 +502,7 @@ const safeProviders: SafeProvider[] = [ accountService, ), deps: [ - KeyServiceAbstraction, + KeyService, DomainSettingsService, ApiServiceAbstraction, I18nServiceAbstraction, @@ -520,7 +521,7 @@ const safeProviders: SafeProvider[] = [ provide: InternalFolderService, useClass: FolderService, deps: [ - KeyServiceAbstraction, + KeyService, EncryptService, I18nServiceAbstraction, CipherServiceAbstraction, @@ -565,7 +566,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: CollectionService, useClass: DefaultCollectionService, - deps: [KeyServiceAbstraction, EncryptService, I18nServiceAbstraction, StateProvider], + deps: [KeyService, EncryptService, I18nServiceAbstraction, StateProvider], }), safeProvider({ provide: ENV_ADDITIONAL_REGIONS, @@ -610,8 +611,8 @@ const safeProviders: SafeProvider[] = [ deps: [CryptoFunctionServiceAbstraction], }), safeProvider({ - provide: KeyServiceAbstraction, - useClass: KeyService, + provide: KeyService, + useClass: DefaultKeyService, deps: [ PinServiceAbstraction, InternalMasterPasswordServiceAbstraction, @@ -636,7 +637,7 @@ const safeProviders: SafeProvider[] = [ useFactory: legacyPasswordGenerationServiceFactory, deps: [ EncryptService, - KeyServiceAbstraction, + KeyService, PolicyServiceAbstraction, AccountServiceAbstraction, StateProvider, @@ -645,7 +646,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: GeneratorHistoryService, useClass: LocalGeneratorHistoryService, - deps: [EncryptService, KeyServiceAbstraction, StateProvider], + deps: [EncryptService, KeyService, StateProvider], }), safeProvider({ provide: UsernameGenerationServiceAbstraction, @@ -653,7 +654,7 @@ const safeProviders: SafeProvider[] = [ deps: [ ApiServiceAbstraction, I18nServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, PolicyServiceAbstraction, AccountServiceAbstraction, @@ -693,7 +694,7 @@ const safeProviders: SafeProvider[] = [ provide: InternalSendService, useClass: SendService, deps: [ - KeyServiceAbstraction, + KeyService, I18nServiceAbstraction, KeyGenerationServiceAbstraction, SendStateProviderAbstraction, @@ -720,7 +721,7 @@ const safeProviders: SafeProvider[] = [ DomainSettingsService, InternalFolderService, CipherServiceAbstraction, - KeyServiceAbstraction, + KeyService, CollectionService, MessagingServiceAbstraction, InternalPolicyService, @@ -753,7 +754,7 @@ const safeProviders: SafeProvider[] = [ AccountServiceAbstraction, PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, - KeyServiceAbstraction, + KeyService, TokenServiceAbstraction, PolicyServiceAbstraction, BiometricStateService, @@ -780,6 +781,7 @@ const safeProviders: SafeProvider[] = [ StateEventRunnerService, TaskSchedulerService, LogService, + BiometricsService, LOCKED_CALLBACK, LOGOUT_CALLBACK, ], @@ -826,7 +828,7 @@ const safeProviders: SafeProvider[] = [ ImportApiServiceAbstraction, I18nServiceAbstraction, CollectionService, - KeyServiceAbstraction, + KeyService, EncryptService, PinServiceAbstraction, AccountServiceAbstraction, @@ -839,7 +841,7 @@ const safeProviders: SafeProvider[] = [ FolderServiceAbstraction, CipherServiceAbstraction, PinServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, CryptoFunctionServiceAbstraction, KdfConfigService, @@ -853,7 +855,7 @@ const safeProviders: SafeProvider[] = [ CipherServiceAbstraction, ApiServiceAbstraction, PinServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, CryptoFunctionServiceAbstraction, CollectionService, @@ -960,7 +962,7 @@ const safeProviders: SafeProvider[] = [ deps: [ AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, - KeyServiceAbstraction, + KeyService, ApiServiceAbstraction, TokenServiceAbstraction, LogService, @@ -974,17 +976,15 @@ const safeProviders: SafeProvider[] = [ provide: UserVerificationServiceAbstraction, useClass: UserVerificationService, deps: [ - KeyServiceAbstraction, + KeyService, AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, I18nServiceAbstraction, UserVerificationApiServiceAbstraction, UserDecryptionOptionsServiceAbstraction, PinServiceAbstraction, - LogService, - VaultTimeoutSettingsServiceAbstraction, - PlatformUtilsServiceAbstraction, KdfConfigService, + BiometricsService, ], }), safeProvider({ @@ -1007,7 +1007,7 @@ const safeProviders: SafeProvider[] = [ deps: [ OrganizationApiServiceAbstraction, AccountServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, OrganizationUserApiService, I18nServiceAbstraction, @@ -1117,7 +1117,7 @@ const safeProviders: SafeProvider[] = [ deps: [ KeyGenerationServiceAbstraction, CryptoFunctionServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, AppIdServiceAbstraction, DevicesApiServiceAbstraction, @@ -1137,7 +1137,7 @@ const safeProviders: SafeProvider[] = [ AppIdServiceAbstraction, AccountServiceAbstraction, InternalMasterPasswordServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, ApiServiceAbstraction, StateProvider, @@ -1231,7 +1231,7 @@ const safeProviders: SafeProvider[] = [ ApiServiceAbstraction, BillingApiServiceAbstraction, ConfigService, - KeyServiceAbstraction, + KeyService, EncryptService, I18nServiceAbstraction, OrganizationApiServiceAbstraction, @@ -1291,7 +1291,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: UserAutoUnlockKeyService, useClass: UserAutoUnlockKeyService, - deps: [KeyServiceAbstraction], + deps: [KeyService], }), safeProvider({ provide: ErrorHandler, @@ -1335,7 +1335,7 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultSetPasswordJitService, deps: [ ApiServiceAbstraction, - KeyServiceAbstraction, + KeyService, EncryptService, I18nServiceAbstraction, KdfConfigService, @@ -1363,7 +1363,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: RegistrationFinishServiceAbstraction, useClass: DefaultRegistrationFinishService, - deps: [KeyServiceAbstraction, AccountApiServiceAbstraction], + deps: [KeyService, AccountApiServiceAbstraction], }), safeProvider({ provide: ViewCacheService, @@ -1390,7 +1390,7 @@ const safeProviders: SafeProvider[] = [ PlatformUtilsServiceAbstraction, AccountServiceAbstraction, KdfConfigService, - KeyServiceAbstraction, + KeyService, ], }), safeProvider({ @@ -1418,7 +1418,7 @@ const safeProviders: SafeProvider[] = [ provide: UserAsymmetricKeysRegenerationService, useClass: DefaultUserAsymmetricKeysRegenerationService, deps: [ - KeyServiceAbstraction, + KeyService, CipherServiceAbstraction, UserAsymmetricKeysRegenerationApiService, LogService, diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index 4aa3a632855..081dafb1706 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -7,14 +7,17 @@ import { UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { KdfConfig, KeyService } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsStatus, + KdfConfig, + KeyService, +} from "@bitwarden/key-management"; import { KdfConfigService } from "../../../../../key-management/src/abstractions/kdf-config.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; import { VaultTimeoutSettingsService } from "../../../abstractions/vault-timeout/vault-timeout-settings.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { HashPurpose } from "../../../platform/enums"; import { Utils } from "../../../platform/misc/utils"; import { UserId } from "../../../types/guid"; @@ -36,10 +39,9 @@ describe("UserVerificationService", () => { const userVerificationApiService = mock(); const userDecryptionOptionsService = mock(); const pinService = mock(); - const logService = mock(); const vaultTimeoutSettingsService = mock(); - const platformUtilsService = mock(); const kdfConfigService = mock(); + const biometricsService = mock(); const mockUserId = Utils.newGuid() as UserId; let accountService: FakeAccountService; @@ -56,10 +58,8 @@ describe("UserVerificationService", () => { userVerificationApiService, userDecryptionOptionsService, pinService, - logService, - vaultTimeoutSettingsService, - platformUtilsService, kdfConfigService, + biometricsService, ); }); @@ -113,26 +113,15 @@ describe("UserVerificationService", () => { ); test.each([ - [true, true, true, true], - [true, true, true, false], - [true, true, false, false], - [false, true, false, true], - [false, false, false, false], - [false, false, true, false], - [false, false, false, true], + [true, BiometricsStatus.Available], + [false, BiometricsStatus.DesktopDisconnected], + [false, BiometricsStatus.HardwareUnavailable], ])( "returns %s for biometrics availability when isBiometricLockSet is %s, hasUserKeyStored is %s, and supportsSecureStorage is %s", - async ( - expectedReturn: boolean, - isBiometricsLockSet: boolean, - isBiometricsUserKeyStored: boolean, - platformSupportSecureStorage: boolean, - ) => { + async (expectedReturn: boolean, biometricsStatus: BiometricsStatus) => { setMasterPasswordAvailability(false); setPinAvailability("DISABLED"); - vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(isBiometricsLockSet); - keyService.hasUserKeyStored.mockResolvedValue(isBiometricsUserKeyStored); - platformUtilsService.supportsSecureStorage.mockReturnValue(platformSupportSecureStorage); + biometricsService.getBiometricsStatus.mockResolvedValue(biometricsStatus); const result = await sut.getAvailableVerificationOptions("client"); diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 822ee70ec5b..2935c1958a4 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -3,17 +3,17 @@ import { firstValueFrom, map } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; -import { KdfConfigService, KeyService } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsStatus, + KdfConfigService, + KeyService, +} from "@bitwarden/key-management"; import { PinServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin.service.abstraction"; -import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../../abstractions/vault-timeout/vault-timeout-settings.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { HashPurpose } from "../../../platform/enums"; -import { KeySuffixOptions } from "../../../platform/enums/key-suffix-options.enum"; import { UserId } from "../../../types/guid"; -import { UserKey } from "../../../types/key"; import { AccountService } from "../../abstractions/account.service"; import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction"; @@ -47,10 +47,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti private userVerificationApiService: UserVerificationApiServiceAbstraction, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private pinService: PinServiceAbstraction, - private logService: LogService, - private vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction, - private platformUtilsService: PlatformUtilsService, private kdfConfigService: KdfConfigService, + private biometricsService: BiometricsService, ) {} async getAvailableVerificationOptions( @@ -58,17 +56,13 @@ export class UserVerificationService implements UserVerificationServiceAbstracti ): Promise { const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; if (verificationType === "client") { - const [ - userHasMasterPassword, - isPinDecryptionAvailable, - biometricsLockSet, - biometricsUserKeyStored, - ] = await Promise.all([ - this.hasMasterPasswordAndMasterKeyHash(userId), - this.pinService.isPinDecryptionAvailable(userId), - this.vaultTimeoutSettingsService.isBiometricLockSet(userId), - this.keyService.hasUserKeyStored(KeySuffixOptions.Biometric, userId), - ]); + const [userHasMasterPassword, isPinDecryptionAvailable, biometricsStatus] = await Promise.all( + [ + this.hasMasterPasswordAndMasterKeyHash(userId), + this.pinService.isPinDecryptionAvailable(userId), + this.biometricsService.getBiometricsStatus(), + ], + ); // note: we do not need to check this.platformUtilsService.supportsBiometric() because // we can just use the logic below which works for both desktop & the browser extension. @@ -77,9 +71,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti client: { masterPassword: userHasMasterPassword, pin: isPinDecryptionAvailable, - biometrics: - biometricsLockSet && - (biometricsUserKeyStored || !this.platformUtilsService.supportsSecureStorage()), + biometrics: biometricsStatus === BiometricsStatus.Available, }, server: { masterPassword: false, @@ -253,17 +245,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti } private async verifyUserByBiometrics(): Promise { - let userKey: UserKey; - // Biometrics crashes and doesn't return a value if the user cancels the prompt - try { - userKey = await this.keyService.getUserKeyFromStorage(KeySuffixOptions.Biometric); - } catch (e) { - this.logService.error(`Biometrics User Verification failed: ${e.message}`); - // So, any failures should be treated as a failed verification - return false; - } - - return userKey != null; + return this.biometricsService.authenticateWithBiometrics(); } async requestOTP() { diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 961d199b06e..8c1d1117c89 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { firstValueFrom, map, timeout } from "rxjs"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { BiometricStateService } from "@bitwarden/key-management"; @@ -24,6 +25,7 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract private vaultTimeoutSettingsService: VaultTimeoutSettingsService, private biometricStateService: BiometricStateService, private accountService: AccountService, + private logService: LogService, ) {} async startProcessReload(authService: AuthService): Promise { diff --git a/libs/common/src/platform/enums/key-suffix-options.enum.ts b/libs/common/src/platform/enums/key-suffix-options.enum.ts index b268c4b777f..98fa215be6a 100644 --- a/libs/common/src/platform/enums/key-suffix-options.enum.ts +++ b/libs/common/src/platform/enums/key-suffix-options.enum.ts @@ -1,5 +1,4 @@ export enum KeySuffixOptions { Auto = "auto", - Biometric = "biometric", Pin = "pin", } diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts index 1350010f849..8a166e63a1f 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts @@ -5,6 +5,7 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; +import { BiometricsService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { SearchService } from "../../abstractions/search.service"; @@ -41,6 +42,7 @@ describe("VaultTimeoutService", () => { let stateEventRunnerService: MockProxy; let taskSchedulerService: MockProxy; let logService: MockProxy; + let biometricsService: MockProxy; let lockedCallback: jest.Mock, [userId: string]>; let loggedOutCallback: jest.Mock, [logoutReason: LogoutReason, userId?: string]>; @@ -66,6 +68,7 @@ describe("VaultTimeoutService", () => { stateEventRunnerService = mock(); taskSchedulerService = mock(); logService = mock(); + biometricsService = mock(); lockedCallback = jest.fn(); loggedOutCallback = jest.fn(); @@ -93,6 +96,7 @@ describe("VaultTimeoutService", () => { stateEventRunnerService, taskSchedulerService, logService, + biometricsService, lockedCallback, loggedOutCallback, ); diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index 55d5bffa99a..8ab10b44b24 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -6,6 +6,7 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; +import { BiometricsService } from "@bitwarden/key-management"; import { SearchService } from "../../abstractions/search.service"; import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; @@ -41,6 +42,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { private stateEventRunnerService: StateEventRunnerService, private taskSchedulerService: TaskSchedulerService, protected logService: LogService, + private biometricService: BiometricsService, private lockedCallback: (userId?: string) => Promise = null, private loggedOutCallback: ( logoutReason: LogoutReason, @@ -98,6 +100,8 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { } async lock(userId?: UserId): Promise { + await this.biometricService.setShouldAutopromptNow(false); + const authed = await this.stateService.getIsAuthenticated({ userId: userId }); if (!authed) { return; diff --git a/libs/key-management/src/angular/index.ts b/libs/key-management/src/angular/index.ts index d7fadc52ce6..1eb9b88b072 100644 --- a/libs/key-management/src/angular/index.ts +++ b/libs/key-management/src/angular/index.ts @@ -3,8 +3,4 @@ */ export { LockComponent } from "./lock/components/lock.component"; -export { - LockComponentService, - BiometricsDisableReason, - UnlockOptions, -} from "./lock/services/lock-component.service"; +export { LockComponentService, UnlockOptions } from "./lock/services/lock-component.service"; diff --git a/libs/key-management/src/angular/lock/components/lock.component.html b/libs/key-management/src/angular/lock/components/lock.component.html index 5f5991c681e..7d9ed6124f6 100644 --- a/libs/key-management/src/angular/lock/components/lock.component.html +++ b/libs/key-management/src/angular/lock/components/lock.component.html @@ -86,12 +86,13 @@

    {{ "or" | i18n }}

    - +
    -
    diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index a798e61aa88..02fa8076086 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -19,9 +19,9 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic 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 { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { SshKeyPasswordPromptComponent } from "@bitwarden/importer/ui"; @@ -56,8 +56,9 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On dialogService: DialogService, datePipe: DatePipe, configService: ConfigService, - private toastService: ToastService, + toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, + sdkService: SdkService, ) { super( cipherService, @@ -78,6 +79,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On datePipe, configService, cipherAuthorizationService, + toastService, + sdkService, ); } @@ -114,17 +117,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On } await super.load(); - - if (!this.editMode || this.cloneMode) { - // Creating an ssh key directly while filtering to the ssh key category - // must force a key to be set. SSH keys must never be created with an empty private key field - if ( - this.cipher.type === CipherType.SshKey && - (this.cipher.sshKey.privateKey == null || this.cipher.sshKey.privateKey === "") - ) { - await this.generateSshKey(false); - } - } } onWindowHidden() { @@ -156,21 +148,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On ); } - async generateSshKey(showNotification: boolean = true) { - const sshKey = await ipc.platform.sshAgent.generateKey("ed25519"); - this.cipher.sshKey.privateKey = sshKey.privateKey; - this.cipher.sshKey.publicKey = sshKey.publicKey; - this.cipher.sshKey.keyFingerprint = sshKey.keyFingerprint; - - if (showNotification) { - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("sshKeyGenerated"), - }); - } - } - async importSshKeyFromClipboard(password: string = "") { const key = await this.platformUtilsService.readFromClipboard(); const parsedKey = await ipc.platform.sshAgent.importKey(key, password); @@ -234,12 +211,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On return await lastValueFrom(dialog.closed); } - async typeChange() { - if (this.cipher.type === CipherType.SshKey) { - await this.generateSshKey(); - } - } - truncateString(value: string, length: number) { return value.length > length ? value.substring(0, length) + "..." : value; } diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts index 59228431e65..78e3b805eb8 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts @@ -15,12 +15,13 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic 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 { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -56,6 +57,8 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implem configService: ConfigService, billingAccountProfileStateService: BillingAccountProfileStateService, cipherAuthorizationService: CipherAuthorizationService, + toastService: ToastService, + sdkService: SdkService, ) { super( cipherService, @@ -78,6 +81,8 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implem configService, billingAccountProfileStateService, cipherAuthorizationService, + toastService, + sdkService, ); } diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.html b/apps/web/src/app/vault/individual-vault/add-edit.component.html index 01ac60fc7e6..2589081d137 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.html +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.html @@ -35,6 +35,7 @@ [(ngModel)]="cipher.type" class="form-control" [disabled]="cipher.isDeleted" + (change)="typeChange()" appAutofocus > diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 53a9e839064..64118e47ee8 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -21,13 +21,14 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic 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 { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -73,6 +74,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On configService: ConfigService, private billingAccountProfileStateService: BillingAccountProfileStateService, cipherAuthorizationService: CipherAuthorizationService, + toastService: ToastService, + sdkService: SdkService, ) { super( cipherService, @@ -93,6 +96,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On datePipe, configService, cipherAuthorizationService, + toastService, + sdkService, ); } diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html index 3f46cb803cf..8ac6138db7c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html @@ -99,6 +99,10 @@ {{ "note" | i18n }} + 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 29c9f14e2aa..5bcdaf56bbd 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 @@ -18,19 +18,25 @@ import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeModule, ButtonModule, CompactModeService, + DialogService, IconButtonModule, ItemModule, SectionComponent, SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; -import { OrgIconDirective, PasswordRepromptService } from "@bitwarden/vault"; +import { + DecryptionFailureDialogComponent, + OrgIconDirective, + PasswordRepromptService, +} from "@bitwarden/vault"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; @@ -55,6 +61,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options ItemMoreOptionsComponent, OrgIconDirective, ScrollingModule, + DecryptionFailureDialogComponent, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", @@ -158,6 +165,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit { private cipherService: CipherService, private router: Router, private platformUtilsService: PlatformUtilsService, + private dialogService: DialogService, ) {} async ngAfterViewInit() { @@ -209,6 +217,13 @@ export class VaultListItemsContainerComponent implements AfterViewInit { this.viewCipherTimeout = window.setTimeout( async () => { try { + if (cipher.decryptionFailure) { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: [cipher.id as CipherId], + }); + return; + } + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); if (!repromptPassed) { return; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 9970c115bb7..a3d8f3ffe31 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -1,15 +1,17 @@ import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, DestroyRef, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { RouterLink } from "@angular/router"; import { combineLatest, Observable, shareReplay, switchMap } from "rxjs"; +import { filter, map, take } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { ButtonModule, Icons, NoItemsModule } from "@bitwarden/components"; -import { VaultIcons } from "@bitwarden/vault"; +import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components"; +import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; @@ -52,6 +54,7 @@ enum VaultState { NewItemDropdownV2Component, ScrollingModule, VaultHeaderV2Component, + DecryptionFailureDialogComponent, ], providers: [VaultUiOnboardingService], }) @@ -89,6 +92,9 @@ export class VaultV2Component implements OnInit, OnDestroy { private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupListFiltersService: VaultPopupListFiltersService, private vaultUiOnboardingService: VaultUiOnboardingService, + private destroyRef: DestroyRef, + private cipherService: CipherService, + private dialogService: DialogService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -116,6 +122,19 @@ export class VaultV2Component implements OnInit, OnDestroy { async ngOnInit() { await this.vaultUiOnboardingService.showOnboardingDialog(); + + this.cipherService.failedToDecryptCiphers$ + .pipe( + map((ciphers) => ciphers.filter((c) => !c.isDeleted)), + filter((ciphers) => ciphers.length > 0), + take(1), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((ciphers) => { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: ciphers.map((c) => c.id as CipherId), + }); + }); } ngOnDestroy(): void {} diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 5b0eb63998d..966793921d7 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -58,6 +58,7 @@ describe("VaultPopupItemsService", () => { cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList); cipherServiceMock.ciphers$ = new BehaviorSubject(null); cipherServiceMock.localData$ = new BehaviorSubject(null); + cipherServiceMock.failedToDecryptCiphers$ = new BehaviorSubject([]); searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers); cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) => ciphers.filter((c) => ["0", "1"].includes(c.id)), @@ -294,21 +295,6 @@ describe("VaultPopupItemsService", () => { }); }); - it("should sort by last used then by name by default", (done) => { - service.remainingCiphers$.subscribe(() => { - expect(cipherServiceMock.getLocaleSortingFunction).toHaveBeenCalled(); - done(); - }); - }); - - it("should NOT sort by last used then by name when search text is applied", (done) => { - service.applyFilter("Login"); - service.remainingCiphers$.subscribe(() => { - expect(cipherServiceMock.getLocaleSortingFunction).not.toHaveBeenCalled(); - done(); - }); - }); - it("should filter remainingCiphers$ down to search term", (done) => { const cipherList = Object.values(allCiphers); const searchText = "Login"; diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index 93aa8cdaba9..1c19a9d8d1d 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -90,6 +90,8 @@ export class VaultPopupItemsService { tap(() => this._ciphersLoading$.next()), waitUntilSync(this.syncService), switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), + withLatestFrom(this.cipherService.failedToDecryptCiphers$), + map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -190,11 +192,6 @@ export class VaultPopupItemsService { (cipher) => !autoFillCiphers.includes(cipher) && !favoriteCiphers.includes(cipher), ), ), - withLatestFrom(this._hasSearchText$), - map(([ciphers, hasSearchText]) => - // Do not sort alphabetically when there is search text, default to the search service scoring - hasSearchText ? ciphers : ciphers.sort(this.cipherService.getLocaleSortingFunction()), - ), shareReplay({ refCount: false, bufferSize: 1 }), ); diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html index a79b6c74b03..dce3ba640d3 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html @@ -27,7 +27,12 @@ [bitMenuTriggerFor]="moreOptions" > -
    -

    {{ "deviceListDescription" | i18n }}

    +

    {{ "deviceListDescriptionTemp" | i18n }}

    @@ -63,13 +63,14 @@ }} {{ row.firstLogin | date: "medium" }} - + diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 3536e9339b3..001918ef495 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9995,6 +9995,9 @@ "deviceListDescription": { "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, From 8cabb36c99a7e51af3225f45b4f8a58b042bad68 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 9 Jan 2025 20:23:55 +0100 Subject: [PATCH 123/270] [PM-16699] Add decrypt trace for decrypt failures (#12749) * Improve decrypt failure logging * Rename decryptcontext to decrypttrace * Improve docs * Revert changes to decrypt logic * Revert keyservice decryption logic change * Undo one more change to decrypt logic --- .../master-password.service.ts | 12 +++++-- .../platform/abstractions/encrypt.service.ts | 24 +++++++++++-- .../src/platform/models/domain/domain-base.ts | 21 +++++++++-- .../src/platform/models/domain/enc-string.ts | 24 ++++++++----- .../encrypt.service.implementation.ts | 28 ++++++++++----- .../src/tools/send/models/domain/send.spec.ts | 7 +++- .../vault/models/domain/attachment.spec.ts | 6 ++-- .../src/vault/models/domain/attachment.ts | 7 +++- libs/common/src/vault/models/domain/card.ts | 7 +++- libs/common/src/vault/models/domain/cipher.ts | 35 +++++++++++++++---- .../src/vault/models/domain/identity.ts | 7 +++- .../src/vault/models/domain/login-uri.ts | 7 +++- libs/common/src/vault/models/domain/login.ts | 4 ++- .../src/vault/models/domain/password.ts | 1 + .../src/vault/models/domain/secure-note.ts | 8 +++-- .../common/src/vault/models/domain/ssh-key.ts | 7 +++- libs/key-management/src/key.service.spec.ts | 1 + libs/key-management/src/key.service.ts | 2 +- 18 files changed, 165 insertions(+), 43 deletions(-) diff --git a/libs/common/src/auth/services/master-password/master-password.service.ts b/libs/common/src/auth/services/master-password/master-password.service.ts index ea6e1045c10..3ac00adf8e5 100644 --- a/libs/common/src/auth/services/master-password/master-password.service.ts +++ b/libs/common/src/auth/services/master-password/master-password.service.ts @@ -180,10 +180,18 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr let decUserKey: Uint8Array; if (userKey.encryptionType === EncryptionType.AesCbc256_B64) { - decUserKey = await this.encryptService.decryptToBytes(userKey, masterKey); + decUserKey = await this.encryptService.decryptToBytes( + userKey, + masterKey, + "Content: User Key; Encrypting Key: Master Key", + ); } else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { const newKey = await this.keyGenerationService.stretchKey(masterKey); - decUserKey = await this.encryptService.decryptToBytes(userKey, newKey); + decUserKey = await this.encryptService.decryptToBytes( + userKey, + newKey, + "Content: User Key; Encrypting Key: Stretched Master Key", + ); } else { throw new Error("Unsupported encryption type."); } diff --git a/libs/common/src/platform/abstractions/encrypt.service.ts b/libs/common/src/platform/abstractions/encrypt.service.ts index 5b28b98803b..a660524699d 100644 --- a/libs/common/src/platform/abstractions/encrypt.service.ts +++ b/libs/common/src/platform/abstractions/encrypt.service.ts @@ -8,12 +8,32 @@ import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export abstract class EncryptService { abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise; abstract encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an EncString to a string + * @param encString - The EncString to decrypt + * @param key - The key to decrypt the EncString with + * @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include + * sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt + * @returns The decrypted string + */ abstract decryptToUtf8( encString: EncString, key: SymmetricCryptoKey, - decryptContext?: string, + decryptTrace?: string, ): Promise; - abstract decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an Encrypted object to a Uint8Array + * @param encThing - The Encrypted object to decrypt + * @param key - The key to decrypt the Encrypted object with + * @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include + * sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt + * @returns The decrypted Uint8Array + */ + abstract decryptToBytes( + encThing: Encrypted, + key: SymmetricCryptoKey, + decryptTrace?: string, + ): Promise; abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise; abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise; abstract resolveLegacyKey(key: SymmetricCryptoKey, encThing: Encrypted): SymmetricCryptoKey; diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index bd9139999b7..688cf52d4c0 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -63,6 +63,7 @@ export default class Domain { map: any, orgId: string, key: SymmetricCryptoKey = null, + objectContext: string = "No Domain Context", ): Promise { const promises = []; const self: any = this; @@ -78,7 +79,11 @@ export default class Domain { .then(() => { const mapProp = map[theProp] || theProp; if (self[mapProp]) { - return self[mapProp].decrypt(orgId, key); + return self[mapProp].decrypt( + orgId, + key, + `Property: ${prop}; ObjectContext: ${objectContext}`, + ); } return null; }) @@ -114,12 +119,21 @@ export default class Domain { key: SymmetricCryptoKey, encryptService: EncryptService, _: Constructor = this.constructor as Constructor, + objectContext: string = "No Domain Context", ): Promise> { const promises = []; for (const prop of encryptedProperties) { const value = (this as any)[prop] as EncString; - promises.push(this.decryptProperty(prop, value, key, encryptService)); + promises.push( + this.decryptProperty( + prop, + value, + key, + encryptService, + `Property: ${prop.toString()}; ObjectContext: ${objectContext}`, + ), + ); } const decryptedObjects = await Promise.all(promises); @@ -137,10 +151,11 @@ export default class Domain { value: EncString, key: SymmetricCryptoKey, encryptService: EncryptService, + decryptTrace: string, ) { let decrypted: string = null; if (value) { - decrypted = await value.decryptWithKey(key, encryptService); + decrypted = await value.decryptWithKey(key, encryptService, decryptTrace); } else { decrypted = null; } diff --git a/libs/common/src/platform/models/domain/enc-string.ts b/libs/common/src/platform/models/domain/enc-string.ts index c484c80ee5b..b8e0006942a 100644 --- a/libs/common/src/platform/models/domain/enc-string.ts +++ b/libs/common/src/platform/models/domain/enc-string.ts @@ -156,21 +156,21 @@ export class EncString implements Encrypted { return EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE[encType] === encPieces.length; } - async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise { + async decrypt(orgId: string, key: SymmetricCryptoKey = null, context?: string): Promise { if (this.decryptedValue != null) { return this.decryptedValue; } - let keyContext = "provided-key"; + let decryptTrace = "provided-key"; try { if (key == null) { key = await this.getKeyForDecryption(orgId); - keyContext = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey"; + decryptTrace = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey"; if (orgId != null) { - keyContext = `domain-orgkey-${orgId}`; + decryptTrace = `domain-orgkey-${orgId}`; } else { const cryptoService = Utils.getContainerService().getKeyService(); - keyContext = + decryptTrace = (await cryptoService.getUserKey()) == null ? "domain-withlegacysupport-masterkey" : "domain-withlegacysupport-userkey"; @@ -181,20 +181,28 @@ export class EncString implements Encrypted { } const encryptService = Utils.getContainerService().getEncryptService(); - this.decryptedValue = await encryptService.decryptToUtf8(this, key, keyContext); + this.decryptedValue = await encryptService.decryptToUtf8( + this, + key, + decryptTrace == null ? context : `${decryptTrace}${context || ""}`, + ); } catch (e) { this.decryptedValue = DECRYPT_ERROR; } return this.decryptedValue; } - async decryptWithKey(key: SymmetricCryptoKey, encryptService: EncryptService) { + async decryptWithKey( + key: SymmetricCryptoKey, + encryptService: EncryptService, + decryptTrace: string = "domain-withkey", + ): Promise { try { if (key == null) { throw new Error("No key to decrypt EncString"); } - this.decryptedValue = await encryptService.decryptToUtf8(this, key, "domain-withkey"); + this.decryptedValue = await encryptService.decryptToUtf8(this, key, decryptTrace); } catch (e) { this.decryptedValue = DECRYPT_ERROR; } diff --git a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts index 0a85b34eba8..db353f51c98 100644 --- a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts +++ b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts @@ -114,7 +114,7 @@ export class EncryptServiceImplementation implements EncryptService { const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); if (!macsEqual) { this.logMacFailed( - "[Encrypt service] MAC comparison failed. Key or payload has changed. Key type " + + "[Encrypt service] decryptToUtf8 MAC comparison failed. Key or payload has changed. Key type " + encryptionTypeName(key.encType) + "Payload type " + encryptionTypeName(encString.encryptionType) + @@ -128,7 +128,11 @@ export class EncryptServiceImplementation implements EncryptService { return await this.cryptoFunctionService.aesDecryptFast(fastParams, "cbc"); } - async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise { + async decryptToBytes( + encThing: Encrypted, + key: SymmetricCryptoKey, + decryptContext: string = "no context", + ): Promise { if (key == null) { throw new Error("No encryption key provided."); } @@ -145,7 +149,9 @@ export class EncryptServiceImplementation implements EncryptService { "[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -155,7 +161,9 @@ export class EncryptServiceImplementation implements EncryptService { "[Encrypt service] Key encryption type does not match payload encryption type. Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -167,11 +175,13 @@ export class EncryptServiceImplementation implements EncryptService { const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256"); if (computedMac === null) { this.logMacFailed( - "[Encrypt service] Failed to compute MAC." + + "[Encrypt service#decryptToBytes] Failed to compute MAC." + " Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -179,11 +189,13 @@ export class EncryptServiceImplementation implements EncryptService { const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac); if (!macsMatch) { this.logMacFailed( - "[Encrypt service] MAC comparison failed. Key or payload has changed." + + "[Encrypt service#decryptToBytes]: MAC comparison failed. Key or payload has changed." + " Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index 74c0e77b394..acdb96f0e0d 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -123,7 +123,12 @@ describe("Send", () => { const view = await send.decrypt(); expect(text.decrypt).toHaveBeenNthCalledWith(1, "cryptoKey"); - expect(send.name.decrypt).toHaveBeenNthCalledWith(1, null, "cryptoKey"); + expect(send.name.decrypt).toHaveBeenNthCalledWith( + 1, + null, + "cryptoKey", + "Property: name; ObjectContext: No Domain Context", + ); expect(view).toMatchObject({ id: "id", diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index 14dec8dea0c..b074e7a46ad 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -101,7 +101,7 @@ describe("Attachment", () => { it("uses the provided key without depending on KeyService", async () => { const providedKey = mock(); - await attachment.decrypt(null, providedKey); + await attachment.decrypt(null, "", providedKey); expect(keyService.getUserKeyWithLegacySupport).not.toHaveBeenCalled(); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey); @@ -111,7 +111,7 @@ describe("Attachment", () => { const orgKey = mock(); keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); - await attachment.decrypt("orgId", null); + await attachment.decrypt("orgId", "", null); expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId"); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey); @@ -121,7 +121,7 @@ describe("Attachment", () => { const userKey = mock(); keyService.getUserKeyWithLegacySupport.mockResolvedValue(userKey); - await attachment.decrypt(null, null); + await attachment.decrypt(null, "", null); expect(keyService.getUserKeyWithLegacySupport).toHaveBeenCalled(); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey); diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 1178f441c5e..2b893e33f49 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -38,7 +38,11 @@ export class Attachment extends Domain { ); } - async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { const view = await this.decryptObj( new AttachmentView(this), { @@ -46,6 +50,7 @@ export class Attachment extends Domain { }, orgId, encKey, + "DomainType: Attachment; " + context, ); if (this.key != null) { diff --git a/libs/common/src/vault/models/domain/card.ts b/libs/common/src/vault/models/domain/card.ts index 739cbf78465..fccfe3f595b 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -37,7 +37,11 @@ export class Card extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new CardView(), { @@ -50,6 +54,7 @@ export class Card extends Domain { }, orgId, encKey, + "DomainType: Card; " + context, ); } diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 437afe2e938..d82f4585e65 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -136,7 +136,11 @@ export class Cipher extends Domain implements Decryptable { if (this.key != null) { const encryptService = Utils.getContainerService().getEncryptService(); - const keyBytes = await encryptService.decryptToBytes(this.key, encKey); + const keyBytes = await encryptService.decryptToBytes( + this.key, + encKey, + `Cipher Id: ${this.id}; Content: CipherKey; IsEncryptedByOrgKey: ${this.organizationId != null}`, + ); if (keyBytes == null) { model.name = "[error: cannot decrypt]"; model.decryptionFailure = true; @@ -158,19 +162,36 @@ export class Cipher extends Domain implements Decryptable { switch (this.type) { case CipherType.Login: - model.login = await this.login.decrypt(this.organizationId, bypassValidation, encKey); + model.login = await this.login.decrypt( + this.organizationId, + bypassValidation, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.SecureNote: - model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey); + model.secureNote = await this.secureNote.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.Card: - model.card = await this.card.decrypt(this.organizationId, encKey); + model.card = await this.card.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); break; case CipherType.Identity: - model.identity = await this.identity.decrypt(this.organizationId, encKey); + model.identity = await this.identity.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.SshKey: - model.sshKey = await this.sshKey.decrypt(this.organizationId, encKey); + model.sshKey = await this.sshKey.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; default: break; @@ -181,7 +202,7 @@ export class Cipher extends Domain implements Decryptable { await this.attachments.reduce((promise, attachment) => { return promise .then(() => { - return attachment.decrypt(this.organizationId, encKey); + return attachment.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); }) .then((decAttachment) => { attachments.push(decAttachment); diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index e2b7aef52f0..570e6c0b4d5 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -61,7 +61,11 @@ export class Identity extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context: string = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new IdentityView(), { @@ -86,6 +90,7 @@ export class Identity extends Domain { }, orgId, encKey, + "DomainType: Identity; " + context, ); } diff --git a/libs/common/src/vault/models/domain/login-uri.ts b/libs/common/src/vault/models/domain/login-uri.ts index 0d7380e034d..36782a81502 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -33,7 +33,11 @@ export class LoginUri extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context: string = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new LoginUriView(this), { @@ -41,6 +45,7 @@ export class LoginUri extends Domain { }, orgId, encKey, + context, ); } diff --git a/libs/common/src/vault/models/domain/login.ts b/libs/common/src/vault/models/domain/login.ts index a0a61a9b857..f9a85cd818e 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -55,6 +55,7 @@ export class Login extends Domain { async decrypt( orgId: string, bypassValidation: boolean, + context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { const view = await this.decryptObj( @@ -66,6 +67,7 @@ export class Login extends Domain { }, orgId, encKey, + `DomainType: Login; ${context}`, ); if (this.uris != null) { @@ -76,7 +78,7 @@ export class Login extends Domain { continue; } - const uri = await this.uris[i].decrypt(orgId, encKey); + const uri = await this.uris[i].decrypt(orgId, context, encKey); // URIs are shared remotely after decryption // we need to validate that the string hasn't been changed by a compromised server // This validation is tied to the existence of cypher.key for backwards compatibility diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index 4c4f465654e..48063f495f0 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -32,6 +32,7 @@ export class Password extends Domain { }, orgId, encKey, + "DomainType: PasswordHistory", ); } diff --git a/libs/common/src/vault/models/domain/secure-note.ts b/libs/common/src/vault/models/domain/secure-note.ts index 4769ad062d9..693ae38d9fb 100644 --- a/libs/common/src/vault/models/domain/secure-note.ts +++ b/libs/common/src/vault/models/domain/secure-note.ts @@ -20,8 +20,12 @@ export class SecureNote extends Domain { this.type = obj.type; } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { - return Promise.resolve(new SecureNoteView(this)); + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { + return new SecureNoteView(this); } toSecureNoteData(): SecureNoteData { diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index 3a79c1f0022..9ce16fe4557 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -32,7 +32,11 @@ export class SshKey extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new SshKeyView(), { @@ -42,6 +46,7 @@ export class SshKey extends Domain { }, orgId, encKey, + "DomainType: SshKey; " + context, ); } diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 142a8bbeb86..b77c14ff532 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -462,6 +462,7 @@ describe("keyService", () => { expect(encryptService.decryptToBytes).toHaveBeenCalledWith( fakeEncryptedUserPrivateKey, userKey, + "Content: Encrypted Private Key", ); expect(userPrivateKey).toBe(fakeDecryptedUserPrivateKey); diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index fba0ce60b74..b1debccb95d 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -382,7 +382,6 @@ export class DefaultKeyService implements KeyServiceAbstraction { key: org.key, }; }); - return encOrgKeyData; }); } @@ -891,6 +890,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { return (await this.encryptService.decryptToBytes( new EncString(encryptedPrivateKey), key, + "Content: Encrypted Private Key", )) as UserPrivateKey; } From 6ef3e9a07673ab9d332aa880cb1f212688e1c446 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 9 Jan 2025 18:58:22 -0500 Subject: [PATCH 124/270] [PM-16831] TS Strict crypto function service (#12737) * strict types in crypto function services * Improve aesDecrypt types --- .../abstractions/crypto-function.service.ts | 14 +++--- .../models/domain/decrypt-parameters.ts | 15 +++--- .../models/domain/symmetric-crypto-key.ts | 10 ++-- .../encrypt.service.implementation.ts | 2 +- .../web-crypto-function.service.spec.ts | 33 +++++++++---- .../services/web-crypto-function.service.ts | 47 ++++++++++++------- .../node-crypto-function.service.spec.ts | 26 ++++++---- .../services/node-crypto-function.service.ts | 37 ++++++++++----- 8 files changed, 117 insertions(+), 67 deletions(-) diff --git a/libs/common/src/platform/abstractions/crypto-function.service.ts b/libs/common/src/platform/abstractions/crypto-function.service.ts index 18c14677dd0..56b0ee55afe 100644 --- a/libs/common/src/platform/abstractions/crypto-function.service.ts +++ b/libs/common/src/platform/abstractions/crypto-function.service.ts @@ -1,5 +1,5 @@ import { CsprngArray } from "../../types/csprng"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { CbcDecryptParameters, EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export abstract class CryptoFunctionService { @@ -51,11 +51,13 @@ export abstract class CryptoFunctionService { iv: string, mac: string, key: SymmetricCryptoKey, - ): DecryptParameters; - abstract aesDecryptFast( - parameters: DecryptParameters, - mode: "cbc" | "ecb", - ): Promise; + ): CbcDecryptParameters; + abstract aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise; abstract aesDecrypt( data: Uint8Array, iv: Uint8Array, diff --git a/libs/common/src/platform/models/domain/decrypt-parameters.ts b/libs/common/src/platform/models/domain/decrypt-parameters.ts index 784826d3bd2..d3b4bf60d42 100644 --- a/libs/common/src/platform/models/domain/decrypt-parameters.ts +++ b/libs/common/src/platform/models/domain/decrypt-parameters.ts @@ -1,10 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -export class DecryptParameters { +export type CbcDecryptParameters = { encKey: T; data: T; iv: T; - macKey: T; - mac: T; + macKey?: T; + mac?: T; macData: T; -} +}; + +export type EcbDecryptParameters = { + encKey: T; + data: T; +}; diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index f467cb8d6e4..eab4c7b2114 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -7,7 +7,7 @@ import { EncryptionType } from "../../enums"; export class SymmetricCryptoKey { key: Uint8Array; - encKey?: Uint8Array; + encKey: Uint8Array; macKey?: Uint8Array; encType: EncryptionType; @@ -48,12 +48,8 @@ export class SymmetricCryptoKey { throw new Error("Unsupported encType/key length."); } - if (this.key != null) { - this.keyB64 = Utils.fromBufferToB64(this.key); - } - if (this.encKey != null) { - this.encKeyB64 = Utils.fromBufferToB64(this.encKey); - } + this.keyB64 = Utils.fromBufferToB64(this.key); + this.encKeyB64 = Utils.fromBufferToB64(this.encKey); if (this.macKey != null) { this.macKeyB64 = Utils.fromBufferToB64(this.macKey); } diff --git a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts index db353f51c98..68263cadf27 100644 --- a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts +++ b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts @@ -125,7 +125,7 @@ export class EncryptServiceImplementation implements EncryptService { } } - return await this.cryptoFunctionService.aesDecryptFast(fastParams, "cbc"); + return await this.cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters: fastParams }); } async decryptToBytes( diff --git a/libs/common/src/platform/services/web-crypto-function.service.spec.ts b/libs/common/src/platform/services/web-crypto-function.service.spec.ts index 71f2828855f..1929e6454ef 100644 --- a/libs/common/src/platform/services/web-crypto-function.service.spec.ts +++ b/libs/common/src/platform/services/web-crypto-function.service.spec.ts @@ -2,7 +2,7 @@ import { mock } from "jest-mock-extended"; import { Utils } from "../../platform/misc/utils"; import { PlatformUtilsService } from "../abstractions/platform-utils.service"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { WebCryptoFunctionService } from "./web-crypto-function.service"; @@ -253,8 +253,13 @@ describe("WebCrypto Function Service", () => { const encData = Utils.fromBufferToB64(encValue); const b64Iv = Utils.fromBufferToB64(iv); const symKey = new SymmetricCryptoKey(key); - const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = cryptoFunctionService.aesDecryptFastParameters( + encData, + b64Iv, + null, + symKey, + ); + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe(value); }); @@ -276,8 +281,8 @@ describe("WebCrypto Function Service", () => { const iv = Utils.fromBufferToB64(makeStaticByteArray(16)); const symKey = new SymmetricCryptoKey(makeStaticByteArray(32)); const data = "ByUF8vhyX4ddU9gcooznwA=="; - const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -287,10 +292,11 @@ describe("WebCrypto Function Service", () => { const cryptoFunctionService = getWebCryptoFunctionService(); const key = makeStaticByteArray(32); const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw=="); - const params = new DecryptParameters(); - params.encKey = Utils.fromBufferToByteString(key); - params.data = Utils.fromBufferToByteString(data); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + encKey: Utils.fromBufferToByteString(key), + data: Utils.fromBufferToByteString(data), + }; + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "ecb", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -304,6 +310,15 @@ describe("WebCrypto Function Service", () => { const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key, "cbc"); expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); }); + + it("throws if iv is not provided", async () => { + const cryptoFunctionService = getWebCryptoFunctionService(); + const key = makeStaticByteArray(32); + const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA=="); + await expect(() => cryptoFunctionService.aesDecrypt(data, null, key, "cbc")).rejects.toThrow( + "IV is required for CBC mode", + ); + }); }); describe("aesDecrypt ECB mode", () => { diff --git a/libs/common/src/platform/services/web-crypto-function.service.ts b/libs/common/src/platform/services/web-crypto-function.service.ts index c0592654849..61edf7a13b1 100644 --- a/libs/common/src/platform/services/web-crypto-function.service.ts +++ b/libs/common/src/platform/services/web-crypto-function.service.ts @@ -1,12 +1,10 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import * as argon2 from "argon2-browser"; import * as forge from "node-forge"; import { Utils } from "../../platform/misc/utils"; import { CsprngArray } from "../../types/csprng"; import { CryptoFunctionService } from "../abstractions/crypto-function.service"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { CbcDecryptParameters, EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export class WebCryptoFunctionService implements CryptoFunctionService { @@ -14,10 +12,14 @@ export class WebCryptoFunctionService implements CryptoFunctionService { private subtle: SubtleCrypto; private wasmSupported: boolean; - constructor(globalContext: Window | typeof global) { - this.crypto = typeof globalContext.crypto !== "undefined" ? globalContext.crypto : null; - this.subtle = - !!this.crypto && typeof this.crypto.subtle !== "undefined" ? this.crypto.subtle : null; + constructor(globalContext: { crypto: Crypto }) { + if (globalContext?.crypto?.subtle == null) { + throw new Error( + "Could not instantiate WebCryptoFunctionService. Could not locate Subtle crypto.", + ); + } + this.crypto = globalContext.crypto; + this.subtle = this.crypto.subtle; this.wasmSupported = this.checkIfWasmSupported(); } @@ -220,7 +222,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService { hmac.update(a); const mac1 = hmac.digest().getBytes(); - hmac.start(null, null); + hmac.start("sha256", null); hmac.update(b); const mac2 = hmac.digest().getBytes(); @@ -239,10 +241,10 @@ export class WebCryptoFunctionService implements CryptoFunctionService { aesDecryptFastParameters( data: string, iv: string, - mac: string, + mac: string | null, key: SymmetricCryptoKey, - ): DecryptParameters { - const p = new DecryptParameters(); + ): CbcDecryptParameters { + const p = {} as CbcDecryptParameters; if (key.meta != null) { p.encKey = key.meta.encKeyByteString; p.macKey = key.meta.macKeyByteString; @@ -275,7 +277,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService { return p; } - aesDecryptFast(parameters: DecryptParameters, mode: "cbc" | "ecb"): Promise { + aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise { const decipher = (forge as any).cipher.createDecipher( this.toWebCryptoAesMode(mode), parameters.encKey, @@ -294,21 +301,27 @@ export class WebCryptoFunctionService implements CryptoFunctionService { async aesDecrypt( data: Uint8Array, - iv: Uint8Array, + iv: Uint8Array | null, key: Uint8Array, mode: "cbc" | "ecb", ): Promise { if (mode === "ecb") { // Web crypto does not support AES-ECB mode, so we need to do this in forge. - const params = new DecryptParameters(); - params.data = this.toByteString(data); - params.encKey = this.toByteString(key); - const result = await this.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + data: this.toByteString(data), + encKey: this.toByteString(key), + }; + const result = await this.aesDecryptFast({ mode: "ecb", parameters }); return Utils.fromByteStringToArray(result); } const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [ "decrypt", ]); + + // CBC + if (iv == null) { + throw new Error("IV is required for CBC mode."); + } const buffer = await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data); return new Uint8Array(buffer); } diff --git a/libs/node/src/services/node-crypto-function.service.spec.ts b/libs/node/src/services/node-crypto-function.service.spec.ts index 61200b92855..3256d85110f 100644 --- a/libs/node/src/services/node-crypto-function.service.spec.ts +++ b/libs/node/src/services/node-crypto-function.service.spec.ts @@ -1,5 +1,5 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters"; +import { EcbDecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { NodeCryptoFunctionService } from "./node-crypto-function.service"; @@ -193,8 +193,8 @@ describe("NodeCrypto Function Service", () => { const iv = Utils.fromBufferToB64(makeStaticByteArray(16)); const symKey = new SymmetricCryptoKey(makeStaticByteArray(32)); const data = "ByUF8vhyX4ddU9gcooznwA=="; - const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); - const decValue = await nodeCryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); + const decValue = await nodeCryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -202,10 +202,11 @@ describe("NodeCrypto Function Service", () => { describe("aesDecryptFast ECB mode", () => { it("should successfully decrypt data", async () => { const nodeCryptoFunctionService = new NodeCryptoFunctionService(); - const params = new DecryptParameters(); - params.encKey = makeStaticByteArray(32); - params.data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw=="); - const decValue = await nodeCryptoFunctionService.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + encKey: makeStaticByteArray(32), + data: Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw=="), + }; + const decValue = await nodeCryptoFunctionService.aesDecryptFast({ mode: "ecb", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -219,6 +220,15 @@ describe("NodeCrypto Function Service", () => { const decValue = await nodeCryptoFunctionService.aesDecrypt(data, iv, key, "cbc"); expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); }); + + it("throws if IV is not provided", async () => { + const nodeCryptoFunctionService = new NodeCryptoFunctionService(); + const key = makeStaticByteArray(32); + const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA=="); + await expect( + async () => await nodeCryptoFunctionService.aesDecrypt(data, null, key, "cbc"), + ).rejects.toThrow("Invalid initialization vector"); + }); }); describe("aesDecrypt ECB mode", () => { @@ -454,7 +464,7 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string, fast = f const cryptoFunctionService = new NodeCryptoFunctionService(); const value = Utils.fromUtf8ToArray("SignMe!!"); const key = Utils.fromUtf8ToArray("secretkey"); - let computedMac: ArrayBuffer = null; + let computedMac: ArrayBuffer; if (fast) { computedMac = await cryptoFunctionService.hmacFast(value, key, algorithm); } else { diff --git a/libs/node/src/services/node-crypto-function.service.ts b/libs/node/src/services/node-crypto-function.service.ts index 74d6de2e34c..c06f18023b4 100644 --- a/libs/node/src/services/node-crypto-function.service.ts +++ b/libs/node/src/services/node-crypto-function.service.ts @@ -1,12 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import * as crypto from "crypto"; import * as forge from "node-forge"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters"; +import { + CbcDecryptParameters, + EcbDecryptParameters, +} from "@bitwarden/common/platform/models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "@bitwarden/common/types/csprng"; @@ -168,10 +169,10 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { aesDecryptFastParameters( data: string, iv: string, - mac: string, + mac: string | null, key: SymmetricCryptoKey, - ): DecryptParameters { - const p = new DecryptParameters(); + ): CbcDecryptParameters { + const p = {} as CbcDecryptParameters; p.encKey = key.encKey; p.data = Utils.fromB64ToArray(data); p.iv = Utils.fromB64ToArray(iv); @@ -191,22 +192,25 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { return p; } - async aesDecryptFast( - parameters: DecryptParameters, - mode: "cbc" | "ecb", - ): Promise { - const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey, mode); + async aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise { + const iv = mode === "cbc" ? parameters.iv : null; + const decBuf = await this.aesDecrypt(parameters.data, iv, parameters.encKey, mode); return Utils.fromBufferToUtf8(decBuf); } aesDecrypt( data: Uint8Array, - iv: Uint8Array, + iv: Uint8Array | null, key: Uint8Array, mode: "cbc" | "ecb", ): Promise { const nodeData = this.toNodeBuffer(data); - const nodeIv = mode === "ecb" ? null : this.toNodeBuffer(iv); + const nodeIv = this.toNodeBufferOrNull(iv); const nodeKey = this.toNodeBuffer(key); const decipher = crypto.createDecipheriv(this.toNodeCryptoAesMode(mode), nodeKey, nodeIv); const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]); @@ -311,6 +315,13 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { return Buffer.from(value); } + private toNodeBufferOrNull(value: Uint8Array | null): Buffer | null { + if (value == null) { + return null; + } + return this.toNodeBuffer(value); + } + private toUint8Buffer(value: Buffer | string | Uint8Array): Uint8Array { let buf: Uint8Array; if (typeof value === "string") { From 138e07eaf7137f810c73cf33cfd010818c8a484f Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Fri, 10 Jan 2025 03:50:21 -0500 Subject: [PATCH 125/270] Add fetch-depth to checkout step (#12782) --- .github/workflows/repository-management.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index a914a2c4a7a..ac2733e765b 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -461,6 +461,7 @@ jobs: - name: Check out main branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: + fetch-depth: 0 ref: main token: ${{ steps.app-token.outputs.token }} From 653b730969fbf6aadf53f1653cd99ffce542d558 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 10 Jan 2025 11:21:38 +0100 Subject: [PATCH 126/270] [PM-16872] Update libs to use explicit dependencies (#12770) Update all libs to use explicit dependencies rather than relying on tsconfig.libs.json. This allows us to more easily understand the dependencies between libs and prevent users from accidentally adding new dependencies. We still use tsconfig.libs (now renamed tsconfig.spec) for tests. --- bitwarden_license/bit-common/tsconfig.json | 2 +- libs/admin-console/jest.config.js | 2 +- libs/admin-console/tsconfig.json | 10 ++++- libs/angular/jest.config.js | 2 +- libs/angular/tsconfig.json | 22 ++++++++++- libs/auth/jest.config.js | 2 +- libs/auth/tsconfig.json | 19 ++++++++- libs/billing/jest.config.js | 2 +- libs/billing/tsconfig.json | 3 +- libs/common/jest.config.js | 2 +- libs/common/tsconfig.json | 12 +++++- .../src/form-field/form-field.stories.ts | 39 ++++++++++++++++++- libs/components/tsconfig.json | 1 - libs/importer/jest.config.js | 2 +- libs/importer/tsconfig.json | 18 ++++++++- libs/key-management/jest.config.js | 2 +- libs/key-management/tsconfig.json | 18 ++++++++- libs/node/jest.config.js | 2 +- libs/node/tsconfig.json | 10 ++++- libs/platform/jest.config.js | 2 +- libs/platform/tsconfig.json | 5 ++- ...{tsconfig.libs.json => tsconfig.spec.json} | 0 libs/tools/card/jest.config.js | 2 +- libs/tools/card/tsconfig.json | 13 ++++++- .../vault-export-core/jest.config.js | 2 +- .../vault-export-core/tsconfig.json | 10 ++++- .../vault-export-ui/jest.config.js | 2 +- .../vault-export-ui/tsconfig.json | 21 +++++++++- .../tools/generator/components/jest.config.js | 2 +- libs/tools/generator/components/tsconfig.json | 15 ++++++- libs/tools/generator/core/jest.config.js | 2 +- libs/tools/generator/core/tsconfig.json | 11 +++++- .../extensions/history/jest.config.js | 2 +- .../extensions/history/tsconfig.json | 11 +++++- .../extensions/legacy/jest.config.js | 2 +- .../generator/extensions/legacy/tsconfig.json | 13 ++++++- .../extensions/navigation/jest.config.js | 2 +- .../extensions/navigation/tsconfig.json | 11 +++++- libs/tools/send/send-ui/jest.config.js | 2 +- libs/tools/send/send-ui/tsconfig.json | 18 ++++++++- libs/vault/jest.config.js | 2 +- libs/vault/tsconfig.json | 20 +++++++++- scripts/test-types.js | 13 +++++-- 43 files changed, 308 insertions(+), 45 deletions(-) rename libs/shared/{tsconfig.libs.json => tsconfig.spec.json} (100%) diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index a0a44f2ab30..7791840950b 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../libs/shared/tsconfig.libs", + "extends": "../../libs/shared/tsconfig", "include": ["src", "spec"], "exclude": ["node_modules", "dist"], "compilerOptions": { diff --git a/libs/admin-console/jest.config.js b/libs/admin-console/jest.config.js index f2a8e6458af..5131753964c 100644 --- a/libs/admin-console/jest.config.js +++ b/libs/admin-console/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/admin-console/tsconfig.json b/libs/admin-console/tsconfig.json index 6004a56fb55..3d22cb2ec51 100644 --- a/libs/admin-console/tsconfig.json +++ b/libs/admin-console/tsconfig.json @@ -1,5 +1,13 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/key-management": ["../key-management/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/angular/jest.config.js b/libs/angular/jest.config.js index c8e748575c0..5e73614eb8e 100644 --- a/libs/angular/jest.config.js +++ b/libs/angular/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/angular/tsconfig.json b/libs/angular/tsconfig.json index 6004a56fb55..6c510f81492 100644 --- a/libs/angular/tsconfig.json +++ b/libs/angular/tsconfig.json @@ -1,5 +1,25 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/generator-components": ["../tools/generator/components/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/importer/core": ["../importer/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault": ["../vault/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/auth/jest.config.js b/libs/auth/jest.config.js index 8bc834c7dab..121d423be17 100644 --- a/libs/auth/jest.config.js +++ b/libs/auth/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json index 6004a56fb55..9be942d38de 100644 --- a/libs/auth/tsconfig.json +++ b/libs/auth/tsconfig.json @@ -1,5 +1,22 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "resolveJsonModule": true, + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/billing/jest.config.js b/libs/billing/jest.config.js index d9bae9633ea..c43606191b9 100644 --- a/libs/billing/jest.config.js +++ b/libs/billing/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/billing/tsconfig.json b/libs/billing/tsconfig.json index 6004a56fb55..bb08eb89d1c 100644 --- a/libs/billing/tsconfig.json +++ b/libs/billing/tsconfig.json @@ -1,5 +1,6 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": {}, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/common/jest.config.js b/libs/common/jest.config.js index d7f78abbf38..7e6c0997b9c 100644 --- a/libs/common/jest.config.js +++ b/libs/common/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../shared/jest.config.ts"); diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index 27d8acbd360..128511ee410 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -1,5 +1,15 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"] + } + }, "include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index ccd80d6fa75..6d8323e088e 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -1,4 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TextFieldModule } from "@angular/cdk/text-field"; +import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; import { AbstractControl, UntypedFormBuilder, @@ -10,7 +13,6 @@ import { } from "@angular/forms"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; -import { A11yTitleDirective } from "@bitwarden/angular/src/directives/a11y-title.directive"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { AsyncActionsModule } from "../async-actions"; @@ -29,6 +31,41 @@ import { I18nMockService } from "../utils/i18n-mock.service"; import { BitFormFieldComponent } from "./form-field.component"; import { FormFieldModule } from "./form-field.module"; +// TOOD: This solves a circular dependency between components and angular. +@Directive({ + selector: "[appA11yTitle]", +}) +export class A11yTitleDirective implements OnInit { + @Input() set appA11yTitle(title: string) { + this.title = title; + this.setAttributes(); + } + + private title: string; + private originalTitle: string | null; + private originalAriaLabel: string | null; + + constructor( + private el: ElementRef, + private renderer: Renderer2, + ) {} + + ngOnInit() { + this.originalTitle = this.el.nativeElement.getAttribute("title"); + this.originalAriaLabel = this.el.nativeElement.getAttribute("aria-label"); + this.setAttributes(); + } + + private setAttributes() { + if (this.originalTitle === null) { + this.renderer.setAttribute(this.el.nativeElement, "title", this.title); + } + if (this.originalAriaLabel === null) { + this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title); + } + } +} + export default { title: "Component Library/Form/Field", component: BitFormFieldComponent, diff --git a/libs/components/tsconfig.json b/libs/components/tsconfig.json index dabcecf78e9..71eef15fac4 100644 --- a/libs/components/tsconfig.json +++ b/libs/components/tsconfig.json @@ -20,7 +20,6 @@ "lib": ["es2020", "dom"], "paths": { "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/angular/*": ["../angular/src/*"], "@bitwarden/platform": ["../platform/src"] }, "plugins": [ diff --git a/libs/importer/jest.config.js b/libs/importer/jest.config.js index 68daba3d407..ab449dc7757 100644 --- a/libs/importer/jest.config.js +++ b/libs/importer/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../shared/jest.config.ts"); diff --git a/libs/importer/tsconfig.json b/libs/importer/tsconfig.json index 6004a56fb55..2235cccb5c7 100644 --- a/libs/importer/tsconfig.json +++ b/libs/importer/tsconfig.json @@ -1,5 +1,21 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/key-management/jest.config.js b/libs/key-management/jest.config.js index e20d02303d9..ad8023e906b 100644 --- a/libs/key-management/jest.config.js +++ b/libs/key-management/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/key-management/tsconfig.json b/libs/key-management/tsconfig.json index 6004a56fb55..8279f14c786 100644 --- a/libs/key-management/tsconfig.json +++ b/libs/key-management/tsconfig.json @@ -1,5 +1,21 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/platform": ["../platform/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/node/jest.config.js b/libs/node/jest.config.js index dc98adf8ddd..1a33ad39406 100644 --- a/libs/node/jest.config.js +++ b/libs/node/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../shared/jest.config.ts"); diff --git a/libs/node/tsconfig.json b/libs/node/tsconfig.json index 6004a56fb55..3d22cb2ec51 100644 --- a/libs/node/tsconfig.json +++ b/libs/node/tsconfig.json @@ -1,5 +1,13 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/key-management": ["../key-management/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/platform/jest.config.js b/libs/platform/jest.config.js index 4649b293a7a..063fb847d8f 100644 --- a/libs/platform/jest.config.js +++ b/libs/platform/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/platform/tsconfig.json b/libs/platform/tsconfig.json index 6004a56fb55..eaa021247d8 100644 --- a/libs/platform/tsconfig.json +++ b/libs/platform/tsconfig.json @@ -1,5 +1,8 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": {} + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/shared/tsconfig.libs.json b/libs/shared/tsconfig.spec.json similarity index 100% rename from libs/shared/tsconfig.libs.json rename to libs/shared/tsconfig.spec.json diff --git a/libs/tools/card/jest.config.js b/libs/tools/card/jest.config.js index b68bda8d5ca..952e9ce0e2e 100644 --- a/libs/tools/card/jest.config.js +++ b/libs/tools/card/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/card/tsconfig.json b/libs/tools/card/tsconfig.json index 52eed3035a6..9c910521cc6 100644 --- a/libs/tools/card/tsconfig.json +++ b/libs/tools/card/tsconfig.json @@ -1,5 +1,16 @@ { - "extends": "../../shared/tsconfig.libs", + "extends": "../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../angular/src/*"], + "@bitwarden/auth/common": ["../../auth/src/common"], + "@bitwarden/common/*": ["../../common/src/*"], + "@bitwarden/components": ["../../components/src"], + "@bitwarden/key-management": ["../../key-management/src"], + "@bitwarden/platform": ["../../platform/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/export/vault-export/vault-export-core/jest.config.js b/libs/tools/export/vault-export/vault-export-core/jest.config.js index 955b8e7763c..0a78a9855dc 100644 --- a/libs/tools/export/vault-export/vault-export-core/jest.config.js +++ b/libs/tools/export/vault-export/vault-export-core/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/export/vault-export/vault-export-core/tsconfig.json b/libs/tools/export/vault-export/vault-export-core/tsconfig.json index 5cb90260371..7652a271044 100644 --- a/libs/tools/export/vault-export/vault-export-core/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-core/tsconfig.json @@ -1,5 +1,13 @@ { - "extends": "../../../../shared/tsconfig.libs", + "extends": "../../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], + "@bitwarden/auth/common": ["../../../../auth/src/common"], + "@bitwarden/common/*": ["../../../../common/src/*"], + "@bitwarden/key-management": ["../../../../key-management/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/export/vault-export/vault-export-ui/jest.config.js b/libs/tools/export/vault-export/vault-export-ui/jest.config.js index 955b8e7763c..0a78a9855dc 100644 --- a/libs/tools/export/vault-export/vault-export-ui/jest.config.js +++ b/libs/tools/export/vault-export/vault-export-ui/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json index 5cb90260371..8c8a04d4b62 100644 --- a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json @@ -1,5 +1,24 @@ { - "extends": "../../../../shared/tsconfig.libs", + "extends": "../../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../../../angular/src/*"], + "@bitwarden/auth/angular": ["../../../../auth/src/angular"], + "@bitwarden/auth/common": ["../../../../auth/src/common"], + "@bitwarden/common/*": ["../../../../common/src/*"], + "@bitwarden/components": ["../../../../components/src"], + "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], + "@bitwarden/generator-history": ["../../../../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../../../../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../../../../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../../../key-management/src"], + "@bitwarden/platform": ["../../../../platform/src"], + "@bitwarden/vault-export-core": [ + "../../../../tools/export/vault-export/vault-export-core/src" + ] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/components/jest.config.js b/libs/tools/generator/components/jest.config.js index c34d909fe8d..bf5e465f398 100644 --- a/libs/tools/generator/components/jest.config.js +++ b/libs/tools/generator/components/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.libs.json"); +const { compilerOptions } = require("../../../shared/tsconfig.spec.json"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/generator/components/tsconfig.json b/libs/tools/generator/components/tsconfig.json index c52bfd7b0df..76b060334f6 100644 --- a/libs/tools/generator/components/tsconfig.json +++ b/libs/tools/generator/components/tsconfig.json @@ -1,5 +1,18 @@ { - "extends": "../../../shared/tsconfig.libs", + "extends": "../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../../angular/src/*"], + "@bitwarden/auth/common": ["../../../auth/src/common"], + "@bitwarden/common/*": ["../../../common/src/*"], + "@bitwarden/components": ["../../../components/src"], + "@bitwarden/generator-core": ["../../../tools/generator/core/src"], + "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], + "@bitwarden/key-management": ["../../../key-management/src"], + "@bitwarden/platform": ["../../../platform/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/core/jest.config.js b/libs/tools/generator/core/jest.config.js index 71ccbc80b67..b052672c4af 100644 --- a/libs/tools/generator/core/jest.config.js +++ b/libs/tools/generator/core/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/generator/core/tsconfig.json b/libs/tools/generator/core/tsconfig.json index 6eec2cc24a9..7c703686b20 100644 --- a/libs/tools/generator/core/tsconfig.json +++ b/libs/tools/generator/core/tsconfig.json @@ -1,5 +1,14 @@ { - "extends": "../../../shared/tsconfig.libs", + "extends": "../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], + "@bitwarden/auth/common": ["../../../auth/src/common"], + "@bitwarden/common/*": ["../../../common/src/*"], + "@bitwarden/generator-core": ["../../../tools/generator/core/src"], + "@bitwarden/key-management": ["../../../key-management/src"] + } + }, "include": [ "src", "../extensions/src/history/generator-history.abstraction.ts", diff --git a/libs/tools/generator/extensions/history/jest.config.js b/libs/tools/generator/extensions/history/jest.config.js index d257f3871c7..f90801cd7c4 100644 --- a/libs/tools/generator/extensions/history/jest.config.js +++ b/libs/tools/generator/extensions/history/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/generator/extensions/history/tsconfig.json b/libs/tools/generator/extensions/history/tsconfig.json index 5cb90260371..5fc1caf014f 100644 --- a/libs/tools/generator/extensions/history/tsconfig.json +++ b/libs/tools/generator/extensions/history/tsconfig.json @@ -1,5 +1,14 @@ { - "extends": "../../../../shared/tsconfig.libs", + "extends": "../../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], + "@bitwarden/auth/common": ["../../../../auth/src/common"], + "@bitwarden/common/*": ["../../../../common/src/*"], + "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], + "@bitwarden/key-management": ["../../../../key-management/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/extensions/legacy/jest.config.js b/libs/tools/generator/extensions/legacy/jest.config.js index d257f3871c7..f90801cd7c4 100644 --- a/libs/tools/generator/extensions/legacy/jest.config.js +++ b/libs/tools/generator/extensions/legacy/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/generator/extensions/legacy/tsconfig.json b/libs/tools/generator/extensions/legacy/tsconfig.json index 5cb90260371..9a09e28ea3d 100644 --- a/libs/tools/generator/extensions/legacy/tsconfig.json +++ b/libs/tools/generator/extensions/legacy/tsconfig.json @@ -1,5 +1,16 @@ { - "extends": "../../../../shared/tsconfig.libs", + "extends": "../../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], + "@bitwarden/auth/common": ["../../../../auth/src/common"], + "@bitwarden/common/*": ["../../../../common/src/*"], + "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], + "@bitwarden/generator-history": ["../../../../tools/generator/extensions/history/src"], + "@bitwarden/generator-navigation": ["../../../../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../../../key-management/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/extensions/navigation/jest.config.js b/libs/tools/generator/extensions/navigation/jest.config.js index d257f3871c7..f90801cd7c4 100644 --- a/libs/tools/generator/extensions/navigation/jest.config.js +++ b/libs/tools/generator/extensions/navigation/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/generator/extensions/navigation/tsconfig.json b/libs/tools/generator/extensions/navigation/tsconfig.json index 5cb90260371..5fc1caf014f 100644 --- a/libs/tools/generator/extensions/navigation/tsconfig.json +++ b/libs/tools/generator/extensions/navigation/tsconfig.json @@ -1,5 +1,14 @@ { - "extends": "../../../../shared/tsconfig.libs", + "extends": "../../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], + "@bitwarden/auth/common": ["../../../../auth/src/common"], + "@bitwarden/common/*": ["../../../../common/src/*"], + "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], + "@bitwarden/key-management": ["../../../../key-management/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/send/send-ui/jest.config.js b/libs/tools/send/send-ui/jest.config.js index b68bda8d5ca..952e9ce0e2e 100644 --- a/libs/tools/send/send-ui/jest.config.js +++ b/libs/tools/send/send-ui/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/send/send-ui/tsconfig.json b/libs/tools/send/send-ui/tsconfig.json index c52bfd7b0df..671154e0a04 100644 --- a/libs/tools/send/send-ui/tsconfig.json +++ b/libs/tools/send/send-ui/tsconfig.json @@ -1,5 +1,21 @@ { - "extends": "../../../shared/tsconfig.libs", + "extends": "../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../../angular/src/*"], + "@bitwarden/auth/common": ["../../../auth/src/common"], + "@bitwarden/common/*": ["../../../common/src/*"], + "@bitwarden/components": ["../../../components/src"], + "@bitwarden/generator-components": ["../../../tools/generator/components/src"], + "@bitwarden/generator-core": ["../../../tools/generator/core/src"], + "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../../../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../../../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../../key-management/src"], + "@bitwarden/platform": ["../../../platform/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/vault/jest.config.js b/libs/vault/jest.config.js index e960eed9f1a..e33c115e8dd 100644 --- a/libs/vault/jest.config.js +++ b/libs/vault/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/vault/tsconfig.json b/libs/vault/tsconfig.json index 6004a56fb55..8318212e81d 100644 --- a/libs/vault/tsconfig.json +++ b/libs/vault/tsconfig.json @@ -1,5 +1,23 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "resolveJsonModule": true, + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/generator-components": ["../tools/generator/components/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/vault": ["../vault/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/scripts/test-types.js b/scripts/test-types.js index 2155406cdf0..9534558af30 100644 --- a/scripts/test-types.js +++ b/scripts/test-types.js @@ -16,10 +16,15 @@ function getFiles(dir) { return results; } -const files = getFiles(path.join(__dirname, "..", "libs")).filter((file) => { - const name = path.basename(file); - return name === "tsconfig.spec.json"; -}); +const files = getFiles(path.join(__dirname, "..", "libs")) + .filter((file) => { + const name = path.basename(file); + return name === "tsconfig.spec.json"; + }) + .filter((path) => { + // Exclude shared since it's not actually a library + return !path.includes("libs/shared/"); + }); concurrently([ { From c1e3836cc32ad524bc4ab739375d07dcc63bdd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:16:51 +0000 Subject: [PATCH 127/270] [PM-13758] Ensure decrypted cipherId is used for event collection in get command (#12459) --- apps/cli/src/commands/get.command.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 454db2858ab..a1fec7a7472 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -152,11 +152,9 @@ export class GetCommand extends DownloadCommand { } } - // 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.eventCollectionService.collect( + await this.eventCollectionService.collect( EventType.Cipher_ClientViewed, - id, + decCipher.id, true, decCipher.organizationId, ); From 7df41a9aea1c4fa4095cb1c84b3d479d95aaf607 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:51:12 +0100 Subject: [PATCH 128/270] Fix bad imports identified with #12744 (#12802) Co-authored-by: Daniel James Smith --- .../services/member-access-report.abstraction.ts | 2 +- .../services/member-access-report.service.spec.ts | 2 +- libs/common/src/tools/send/models/domain/send.spec.ts | 2 +- libs/common/src/tools/send/services/send.service.spec.ts | 2 +- .../src/components/lastpass/lastpass-direct-import.service.ts | 2 +- .../generator/components/src/credential-generator.component.ts | 3 +-- .../generator/components/src/password-generator.component.ts | 3 +-- .../generator/components/src/username-generator.component.ts | 3 +-- 8 files changed, 8 insertions(+), 11 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts index 9919da8d08e..cf2f3b6417b 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessExportItem } from "../view/member-access-export.view"; import { MemberAccessReportView } from "../view/member-access-report.view"; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts index 6aab54f77d5..7d6beca48ec 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessReportApiService } from "./member-access-report-api.service"; import { memberAccessReportsMock } from "./member-access-report.mock"; diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index acdb96f0e0d..408a4ea7172 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -2,8 +2,8 @@ import { mock } from "jest-mock-extended"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc } from "../../../../../spec"; import { EncryptService } from "../../../../platform/abstractions/encrypt.service"; import { ContainerService } from "../../../../platform/services/container.service"; diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 5aca3a4b5c9..ff814302513 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -3,8 +3,8 @@ import { firstValueFrom, of } from "rxjs"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, FakeActiveUserState, diff --git a/libs/importer/src/components/lastpass/lastpass-direct-import.service.ts b/libs/importer/src/components/lastpass/lastpass-direct-import.service.ts index 368a9b321be..62826ae3fe9 100644 --- a/libs/importer/src/components/lastpass/lastpass-direct-import.service.ts +++ b/libs/importer/src/components/lastpass/lastpass-direct-import.service.ts @@ -12,9 +12,9 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { DialogService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; -import { DialogService } from "../../../../components/src/dialog"; import { ClientInfo, Vault } from "../../importers/lastpass/access"; import { FederatedUserContext } from "../../importers/lastpass/access/models"; diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 3af9d12abd7..a2b204eaca4 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -22,8 +22,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { IntegrationId } from "@bitwarden/common/tools/integration"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { Option } from "@bitwarden/components/src/select/option"; +import { ToastService, Option } from "@bitwarden/components"; import { AlgorithmInfo, CredentialAlgorithm, diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index 7e230d8bfd6..67a1bf21839 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -19,8 +19,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { Option } from "@bitwarden/components/src/select/option"; +import { ToastService, Option } from "@bitwarden/components"; import { CredentialGeneratorService, Generators, diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index 63293ff7fd9..d0230dcba40 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -23,8 +23,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { IntegrationId } from "@bitwarden/common/tools/integration"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { Option } from "@bitwarden/components/src/select/option"; +import { ToastService, Option } from "@bitwarden/components"; import { AlgorithmInfo, CredentialAlgorithm, From d64cfb67e27623a6bda9a351085d39aa9ece879f Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:15:59 +0100 Subject: [PATCH 129/270] Autosync the updated translations (#12796) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 38 +++ apps/desktop/src/locales/ar/messages.json | 38 +++ apps/desktop/src/locales/az/messages.json | 38 +++ apps/desktop/src/locales/be/messages.json | 38 +++ apps/desktop/src/locales/bg/messages.json | 38 +++ apps/desktop/src/locales/bn/messages.json | 38 +++ apps/desktop/src/locales/bs/messages.json | 38 +++ apps/desktop/src/locales/ca/messages.json | 38 +++ apps/desktop/src/locales/cs/messages.json | 38 +++ apps/desktop/src/locales/cy/messages.json | 38 +++ apps/desktop/src/locales/da/messages.json | 38 +++ apps/desktop/src/locales/de/messages.json | 38 +++ apps/desktop/src/locales/el/messages.json | 38 +++ apps/desktop/src/locales/en_GB/messages.json | 38 +++ apps/desktop/src/locales/en_IN/messages.json | 38 +++ apps/desktop/src/locales/eo/messages.json | 38 +++ apps/desktop/src/locales/es/messages.json | 38 +++ apps/desktop/src/locales/et/messages.json | 38 +++ apps/desktop/src/locales/eu/messages.json | 38 +++ apps/desktop/src/locales/fa/messages.json | 38 +++ apps/desktop/src/locales/fi/messages.json | 38 +++ apps/desktop/src/locales/fil/messages.json | 38 +++ apps/desktop/src/locales/fr/messages.json | 38 +++ apps/desktop/src/locales/gl/messages.json | 38 +++ apps/desktop/src/locales/he/messages.json | 38 +++ apps/desktop/src/locales/hi/messages.json | 38 +++ apps/desktop/src/locales/hr/messages.json | 38 +++ apps/desktop/src/locales/hu/messages.json | 38 +++ apps/desktop/src/locales/id/messages.json | 38 +++ apps/desktop/src/locales/it/messages.json | 40 +++- apps/desktop/src/locales/ja/messages.json | 38 +++ apps/desktop/src/locales/ka/messages.json | 38 +++ apps/desktop/src/locales/km/messages.json | 38 +++ apps/desktop/src/locales/kn/messages.json | 38 +++ apps/desktop/src/locales/ko/messages.json | 38 +++ apps/desktop/src/locales/lt/messages.json | 38 +++ apps/desktop/src/locales/lv/messages.json | 38 +++ apps/desktop/src/locales/me/messages.json | 38 +++ apps/desktop/src/locales/ml/messages.json | 38 +++ apps/desktop/src/locales/mr/messages.json | 38 +++ apps/desktop/src/locales/my/messages.json | 38 +++ apps/desktop/src/locales/nb/messages.json | 38 +++ apps/desktop/src/locales/ne/messages.json | 38 +++ apps/desktop/src/locales/nl/messages.json | 38 +++ apps/desktop/src/locales/nn/messages.json | 38 +++ apps/desktop/src/locales/or/messages.json | 38 +++ apps/desktop/src/locales/pl/messages.json | 38 +++ apps/desktop/src/locales/pt_BR/messages.json | 38 +++ apps/desktop/src/locales/pt_PT/messages.json | 38 +++ apps/desktop/src/locales/ro/messages.json | 38 +++ apps/desktop/src/locales/ru/messages.json | 38 +++ apps/desktop/src/locales/si/messages.json | 38 +++ apps/desktop/src/locales/sk/messages.json | 38 +++ apps/desktop/src/locales/sl/messages.json | 38 +++ apps/desktop/src/locales/sr/messages.json | 38 +++ apps/desktop/src/locales/sv/messages.json | 38 +++ apps/desktop/src/locales/te/messages.json | 38 +++ apps/desktop/src/locales/th/messages.json | 38 +++ apps/desktop/src/locales/tr/messages.json | 38 +++ apps/desktop/src/locales/uk/messages.json | 38 +++ apps/desktop/src/locales/vi/messages.json | 38 +++ apps/desktop/src/locales/zh_CN/messages.json | 50 +++- apps/desktop/src/locales/zh_TW/messages.json | 238 +++++++++++-------- 63 files changed, 2501 insertions(+), 107 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 1f569319953..9339d31aa9e 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januarie" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index a15dbb5f078..bb21e0db35f 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -249,6 +249,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "يناير" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "لم يُعثر على أي منفذ مجاني لتسجيل الدخول." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "السماح" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 07519429f12..82fbee81114 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Yanvar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "SSO giriş üçün açıq port tapıla bilmədi." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, "authorize": { "message": "Səlahiyyət ver" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e89d6b5fa6f..288ba2bede4 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Студзень" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 9770e24c5ad..3d730a67892 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "януари" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Не могат да бъдат открити свободни портове за еднократната идентификация." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, "authorize": { "message": "Упълномощаване" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 8fe871a956f..e4eb247c59e 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ত্রুটি" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "জানুয়ারী" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index e90a7e16c89..9b91d3d7c43 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index ace7e5defc0..2367af72d5c 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gener" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index f73f76f44a5..01d3380d27f 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Leden" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Pro přihlášení SSO nebyly nalezeny žádné volné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrické odemknutí není k dispozici, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, "authorize": { "message": "Autorizovat" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 00d79c80ec8..907f7498f2f 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index b2867c4072e..766f0cf5e71 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Ingen ledige porte fundet til SSO-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, "authorize": { "message": "Godkend" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index ae20bc0931d..d8e946bc3ee 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Es konnten keine freien Ports für die SSO-Anmeldung gefunden werden." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autorisieren" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 676c016c699..b134c0d3db8 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ιανουάριος" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Δεν βρέθηκαν ελεύθερες θύρες για τη σύνδεση sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index ee1be4df2e6..1f705b54329 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorise" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index b8afb36f3a6..bdabdb2f376 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorise" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index b7fbb2fbe93..7cbd15b5965 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januaro" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 9cb82ceb92d..27f694ec189 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 276d3565f1a..ec73ac1e8b2 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Jaanuar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "SSO-ga sisselogimiseks ei leitud ühtegi vaba porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 41e8eda96c7..0147b474ab1 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Urtarrila" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index fdc9ba6d31b..a04c39d4ae0 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -249,6 +249,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ژانویه" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 69092054f28..193553cac83 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tammikuu" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Kertakirjautumiselle ei löytynyt vapaita portteja." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Valtuuta" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index a8a36cd94af..53b2ef9ffba 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index a353653e1e3..f1bf4243874 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvier" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autoriser" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 323d0cd3f7b..35d5cf8c03c 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index dfaa195895e..09d6e321a12 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -249,6 +249,20 @@ "error": { "message": "שגיאה" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ינואר" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 13bb5edfa93..3263191cd31 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index a87261a2027..b1def7c192c 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Pogreška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "siječanj" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Nisu nađeni slobodni portovi za SSO prijavu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autoriziraj" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 11091cde860..fb94d34b387 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérés siker", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "január" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Nem található szabad port az sso bejelentkezéshez." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, "authorize": { "message": "Hitelesítés" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index b9af1c00045..989177b1b43 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index e035a13606d..13e4c692f69 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gennaio" }, @@ -1320,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copia e-mail" + "message": "Copia email" }, "copySecurityCode": { "message": "Copia codice di sicurezza", @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Non è stato possibile trovare nessuna porta libera per il login Sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, "authorize": { "message": "Autorizza" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 7315d74a70f..364de39788b 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -249,6 +249,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1月" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "SSO ログインのための空きポートが見つかりませんでした。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "認可" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 6c554a82700..fef2d56a7c2 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -249,6 +249,20 @@ "error": { "message": "შეცდომა" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "იანვარი" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 323d0cd3f7b..35d5cf8c03c 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 6182bd3a33d..9ce484b3810 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ಜನವರಿ" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 89c5430a5fb..19a13ef3e81 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -249,6 +249,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1월" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 6e07f4ceee3..c1fafc59f12 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Klaida" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Sausis" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index a3db76ac646..f2317e1d0bb 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "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": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvāris" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Netika atrasti brīvi vienotās (SSO) pieteikšanās porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, "authorize": { "message": "Pilnvarot" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 49bf2c60f6c..50a6ee8df58 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index d314ee44578..e1516b99b23 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -249,6 +249,20 @@ "error": { "message": "പിശക്" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ജനുവരി" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 323d0cd3f7b..35d5cf8c03c 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index c1713e0bb32..ce5a50e1f70 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index f264bb5f092..00970fb0fc6 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index fb8d7ec7df5..6a5ae864fc1 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -249,6 +249,20 @@ "error": { "message": "त्रुटि" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "जनवरी" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index a23ef820ab7..729a093927e 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januari" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Er zijn geen vrije poorten gevonden voor de sso-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, "authorize": { "message": "Autoriseren" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 543f20fc01d..7e18c7b77d2 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 2723db4f882..903fe83af70 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ତ୍ରୁଟି" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ଜାନୁଆରୀ" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 9cddf815463..59cf3a96ab9 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Styczeń" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Nie znaleziono wolnych portów dla logowania SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 3e1b8f54040..0e35de82e7c 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Nenhuma porta livre foi encontrada para o cliente final." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autorizar" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 55b031690a8..a63fbca2ebc 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Não foi possível encontrar portas livres para o início de sessão sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autorizar" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 0d26dbb2fc8..939c3295898 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ianuarie" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 056c9d32aca..d8aa6556a0d 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Январь" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Не удалось найти свободные порты для авторизации SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, "authorize": { "message": "Разрешить" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 51625639dda..210ccc7383b 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -249,6 +249,20 @@ "error": { "message": "දෝෂය" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "දුරුතු" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index cec06c3c028..bfcfb56f984 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Január" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Pre prihlásenie SSO sa nepodarilo nájsť žiadne voľné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je z neznámych dôvodov nedostupné." + }, "authorize": { "message": "Autorizovať" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 1973f6c017b..0270b7ba3f1 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Napaka" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 9f9dec2dd68..d8544d8285a 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Јануар" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Нису пронађени портови за SSO пријаву." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Ауторизуј" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index c9a463246dd..023b74b12b4 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 323d0cd3f7b..35d5cf8c03c 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 16961afcc57..b5cc86b1e54 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ข้อผิดพลาด" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "มกราคม" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 8357f8616bc..5f11450a207 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ocak" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "SSO girişi için açık port bulunamadı." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Yetkilendir" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 4103e8b28a6..d4126a6b647 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Січень" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Не знайдено вільних портів для цього входу SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Авторизувати" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index e514124414b..fcd113f3071 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Lỗi" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tháng 1" }, @@ -3362,6 +3376,30 @@ "ssoError": { "message": "Không thể tìm thấy cổng trống để đăng nhập SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 9bff79aa857..0285842f04e 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -249,6 +249,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -975,13 +989,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -1008,7 +1022,7 @@ "message": "账户" }, "loading": { - "message": "加载中…" + "message": "正在加载..." }, "lockVault": { "message": "锁定密码库" @@ -2255,7 +2269,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2495,7 +2509,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2519,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3362,6 +3376,30 @@ "ssoError": { "message": "找不到用于 SSO 登录的可用端口。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, "authorize": { "message": "批准" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 3c7884a22c5..b5ca02d6c8b 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -27,7 +27,7 @@ "message": "安全筆記" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "folders": { "message": "資料夾" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "歡迎回來" }, "moveToOrgDesc": { "message": "選擇您希望將這個項目移動到哪個組織。項目的擁有權將會轉移到該組織。一經移動,您將不再是此項目的直接擁有者。" @@ -181,16 +181,16 @@ "message": "地址" }, "sshPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshPublicKey": { - "message": "Public key" + "message": "公開金鑰" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "指紋" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "金鑰類型" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -208,19 +208,19 @@ "message": "一組SSH金鑰已在之前生成了" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "您輸入的密碼錯誤。" }, "importSshKey": { - "message": "Import" + "message": "匯入" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "確認密碼" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "輸入 SSH 金鑰的密碼" }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "請輸入密碼" }, "sshAgentUnlockRequired": { "message": "請解鎖密碼庫以核准SSh金鑰的請求" @@ -229,7 +229,7 @@ "message": "SSH金鑰請求超時" }, "enableSshAgent": { - "message": "啟用SSH代理" + "message": "啟用 SSH 代理程式" }, "enableSshAgentDesc": { "message": "啟用SSBitwardenH代理以從 Bitwarden 密碼庫簽發SSH請求" @@ -249,6 +249,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "來避免更多資料遺失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -642,10 +656,10 @@ "message": "以通行密鑰 (passkey) 登入" }, "loginWithDevice": { - "message": "Log in with device" + "message": "使用裝置登入" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "使用單一登入" }, "submit": { "message": "送出" @@ -666,7 +680,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", @@ -703,7 +717,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "設定主密碼以完成加入這個組織" }, "settings": { "message": "設定" @@ -712,13 +726,13 @@ "message": "電子郵件帳戶:" }, "requestHint": { - "message": "Request hint" + "message": "請求提示" }, "requestPasswordHint": { "message": "主密碼提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "輸入您帳號的電子郵件,您的密碼提示會傳送給您" }, "passwordHint": { "message": "密碼提示" @@ -764,7 +778,7 @@ "message": "帳戶已建立!現在可以登入了。" }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { "message": "你已經登入!" @@ -854,17 +868,17 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 來存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -881,7 +895,7 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "loginUnavailable": { "message": "登入無法使用" @@ -902,13 +916,13 @@ "message": "指定您本地托管的 Bitwarden 安裝之基礎 URL。" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 URL。例如:https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "適用於進階設定。您可以單獨指定各個服務的網域 URL。" }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" @@ -920,13 +934,13 @@ "message": "伺服器 URL" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "驗證工作階段因時間過久已逾時。請重試登入。" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -969,22 +983,22 @@ "message": "已登出" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "您已經登出了您的帳號。" }, "loginExpired": { "message": "您的登入會話已過期。" }, "restartRegistration": { - "message": "Restart registration" + "message": "重新啟動註冊" }, "expiredLink": { - "message": "Expired link" + "message": "過期連結" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "請重新啟動註冊流程或是重試登入。" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "您可能已經有帳號" }, "logOutConfirmation": { "message": "您確定要登出嗎?" @@ -1041,10 +1055,10 @@ "message": "變更主密碼" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "前往網頁應用程式嗎?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式上變更主密碼。" }, "fingerprintPhrase": { "message": "指紋短語", @@ -1073,16 +1087,16 @@ "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "您的帳戶已被鎖定。" }, "or": { - "message": "or" + "message": "或" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "使用生物辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "unlock": { "message": "解鎖" @@ -1113,7 +1127,7 @@ "message": "密碼庫逾時時間" }, "vaultTimeout1": { - "message": "Timeout" + "message": "逾時" }, "vaultTimeoutDesc": { "message": "選擇密碼庫何時執行密碼庫逾時動作。" @@ -1320,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copySecurityCode": { "message": "複製安全代碼", @@ -1369,7 +1383,7 @@ "message": "您可以在 bitwarden.com 網頁版密碼庫購買進階會員資格。現在要前往嗎?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式的帳號設定中購買進階版。" }, "premiumCurrentMember": { "message": "您目前是進階會員!" @@ -1393,13 +1407,13 @@ "message": "密碼歷史記錄" }, "generatorHistory": { - "message": "Generator history" + "message": "產生器歷史記錄" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "清除產生器歷史記錄" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "若繼續,所有產生器曾經產生的記錄會被刪除。您確定要繼續?" }, "clear": { "message": "清除", @@ -1409,13 +1423,13 @@ "message": "沒有可列出的密碼。" }, "clearHistory": { - "message": "Clear history" + "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "您最近未產生任何密碼" }, "undo": { "message": "復原" @@ -1492,13 +1506,13 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "存取權杖更新失敗" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "未找到存取權杖或 API 密鑰。請重試登出再登入。" }, "help": { "message": "說明" @@ -1588,7 +1602,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "匯出自" }, "exportVault": { "message": "匯出密碼庫" @@ -1720,7 +1734,7 @@ "message": "無效的 PIN 碼。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "輸入太多無效 PIN 碼。 正在登出。" }, "unlockWithWindowsHello": { "message": "使用 Windows Hello 解鎖" @@ -1729,7 +1743,7 @@ "message": "額外的 Windows Hello 設定" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "使用系統驗證解鎖" }, "windowsHelloConsentMessage": { "message": "驗證 Bitwarden。" @@ -1747,7 +1761,7 @@ "message": "啟動應用程式時詢問 Windows Hello" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "在啟動時詢問系統驗證" }, "autoPromptTouchId": { "message": "啟動應用程式時要求 Touch ID" @@ -1759,7 +1773,7 @@ "message": "為提升安全,建議使用。" }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "重啟後使用主密碼鎖定" }, "deleteAccount": { "message": "刪除帳戶" @@ -1771,7 +1785,7 @@ "message": "刪除您的帳戶是永久性的。並且無法復原。" }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "無法刪除帳號" }, "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." @@ -2908,10 +2922,10 @@ "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "需要核准裝置" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "選擇下面的一個核准選項" }, "rememberThisDevice": { "message": "記住這個裝置" @@ -2966,7 +2980,7 @@ "message": "缺少使用者電子郵件地址" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "未找到使用中帳號的電子郵件。正在將您登出。" }, "deviceTrusted": { "message": "裝置已信任" @@ -3072,7 +3086,7 @@ "message": "子選單" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -3130,16 +3144,16 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "使用瀏覽器啟動 Duo" }, "importFormatError": { "message": "資料格式不正確。請檢查匯入檔案後再試一次。" @@ -3154,7 +3168,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3293,7 +3307,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "成功" }, "troubleshooting": { "message": "疑難排解" @@ -3311,13 +3325,13 @@ "message": "密碼金鑰已移除" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "指定目標集合時發生錯誤。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "指定目標資料夾時發生錯誤。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "檢視 $NAME$ 中的項目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3327,7 +3341,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3337,11 +3351,11 @@ } }, "back": { - "message": "Back", + "message": "返回", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "移除 $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3351,67 +3365,91 @@ } }, "data": { - "message": "Data" + "message": "資料" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSends": { - "message": "Text Sends" + "message": "文字 Sends" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "無法找到可用於 SSO 登入的空閒連接埠。" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "由於未在 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "基於不明原因,生物辨識解鎖無法使用。" }, "authorize": { - "message": "Authorize" + "message": "授權" }, "deny": { - "message": "Deny" + "message": "拒絕" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "確認 SSH 密鑰使用" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "正在請求存取權限到" }, "unknownApplication": { - "message": "An application" + "message": "應用程式" }, "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "匯入密碼保護的 SSH 密鑰暫時還未支援" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "SSH 密鑰不正確" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "SSH 密鑰類型不支援" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "從剪貼簿中匯入密鑰" }, "sshKeyPasted": { - "message": "SSH key imported successfully" + "message": "SSH 密鑰成功匯入" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "檔案已儲存到裝置。在您的裝置上管理下載檔案。" }, "importantNotice": { - "message": "Important notice" + "message": "重要通知" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "啟動兩階段登入" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" }, "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": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3458,15 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "不,我不行" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "是,我可以存取我的電子郵件位址" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" } } From fa61928daf02796c1ebe34bd7c1316df0cc0e7f2 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:58:47 -0600 Subject: [PATCH 130/270] [CL] add ToastVariant to CL barrel file (#12804) --- libs/angular/src/directives/copy-click.directive.ts | 3 +-- libs/components/src/toast/index.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/angular/src/directives/copy-click.directive.ts b/libs/angular/src/directives/copy-click.directive.ts index cb346ebf8d3..ece867c09fd 100644 --- a/libs/angular/src/directives/copy-click.directive.ts +++ b/libs/angular/src/directives/copy-click.directive.ts @@ -4,8 +4,7 @@ import { Directive, HostListener, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { ToastVariant } from "@bitwarden/components/src/toast/toast.component"; +import { ToastService, ToastVariant } from "@bitwarden/components"; @Directive({ selector: "[appCopyClick]", diff --git a/libs/components/src/toast/index.ts b/libs/components/src/toast/index.ts index f0b55402194..5afed111c0f 100644 --- a/libs/components/src/toast/index.ts +++ b/libs/components/src/toast/index.ts @@ -1,2 +1,3 @@ export * from "./toast.module"; export * from "./toast.service"; +export type { ToastVariant } from "./toast.component"; From db84ccf935a1069234846b8ba92c72e8b293ac57 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 10 Jan 2025 16:37:34 +0100 Subject: [PATCH 131/270] [PM-16838] Forbid imports containing /src/ (#12744) Forbids bad imports containing /src/. --- .eslintrc.json | 173 +++--------------- .../extension-anon-layout-wrapper.stories.ts | 2 + .../popup/two-factor-auth-duo.component.ts | 12 ++ .../popup/two-factor-auth-email.component.ts | 14 ++ .../auth/popup/two-factor-auth.component.ts | 2 + .../src/autofill/content/notification-bar.ts | 2 + apps/browser/src/autofill/types/index.ts | 2 + .../foreground-vault-timeout.service.ts | 6 +- .../vault-header/vault-header-v2.component.ts | 2 + .../vault-password-history-v2.component.ts | 2 + .../vault-v2/view-v2/view-v2.component.ts | 4 + .../vault-popup-autofill.service.spec.ts | 2 + .../services/vault-popup-autofill.service.ts | 2 + .../vault-popup-items.service.spec.ts | 2 + .../popup/settings/appearance-v2.component.ts | 6 + .../popup/settings/folders-v2.component.ts | 6 + .../src/commands/bw-credential-create.ts | 1 + .../src/commands/bw-credential-update.ts | 1 + .../src/ipc.service.ts | 2 + .../src/native-message.service.ts | 8 + .../src/auth/two-factor-auth-duo.component.ts | 2 + .../src/auth/two-factor-auth.component.ts | 28 +++ .../components/approve-ssh-request.ts | 2 +- .../device-approval/approve.command.ts | 2 + .../device-approval/deny-all.command.ts | 2 + .../device-approval/deny.command.ts | 2 + .../overview/overview.module.ts | 2 + .../guards/project-access.guard.spec.ts | 2 + .../service-account-access.guard.spec.ts | 2 + .../src/services/jslib-services.module.ts | 2 + ...w-device-verification-notice.guard.spec.ts | 2 + .../new-device-verification-notice.guard.ts | 2 + .../services/vault-filter.service.ts | 2 + .../anon-layout-wrapper.stories.ts | 3 +- .../anon-layout/anon-layout.component.ts | 6 + .../anon-layout/anon-layout.stories.ts | 4 + .../input-password.component.ts | 4 + .../input-password/input-password.stories.ts | 3 +- .../registration-start.stories.ts | 3 +- .../set-password-jit.component.ts | 2 + .../models/domain/user-decryption-options.ts | 2 + .../login-email/login-email.service.ts | 2 + .../login-strategy.service.ts | 2 + .../user-decryption-options.service.ts | 2 + .../webauthn-rotate-credential.request.ts | 2 + .../auth/services/anonymous-hub.service.ts | 2 + .../src/auth/services/auth.service.spec.ts | 2 + libs/common/src/auth/services/auth.service.ts | 2 + .../device-trust.service.implementation.ts | 2 + .../services/device-trust.service.spec.ts | 4 + .../services/key-connector.service.spec.ts | 2 + ...-enrollment.service.implementation.spec.ts | 4 + ...reset-enrollment.service.implementation.ts | 2 + .../user-verification.service.spec.ts | 2 + .../user-verification.service.ts | 2 + .../default-process-reload.service.ts | 2 + libs/common/src/platform/misc/utils.ts | 2 + .../platform/models/domain/enc-string.spec.ts | 2 + .../platform/services/container.service.ts | 2 + .../user-auto-unlock-key.service.spec.ts | 2 + .../services/user-auto-unlock-key.service.ts | 2 + .../src/platform/sync/default-sync.service.ts | 6 + .../vault-timeout-settings.service.spec.ts | 2 + .../vault-timeout-settings.service.ts | 2 + .../vault/models/domain/attachment.spec.ts | 2 + .../src/vault/models/domain/cipher.spec.ts | 2 + .../src/vault/services/cipher.service.spec.ts | 2 + .../src/vault/services/cipher.service.ts | 2 + .../services/folder/folder.service.spec.ts | 2 + .../vault/services/folder/folder.service.ts | 2 + .../src/async-actions/in-forms.stories.ts | 2 + .../src/checkbox/checkbox.stories.ts | 2 + .../src/chip-select/chip-select.component.ts | 2 + .../src/abstractions/key.service.ts | 16 ++ .../src/biometrics/biometric-state.service.ts | 6 + .../src/biometrics/biometric.state.ts | 6 + .../src/kdf-config.service.spec.ts | 4 + libs/key-management/src/kdf-config.service.ts | 4 + libs/key-management/src/key.service.spec.ts | 42 +++++ libs/key-management/src/key.service.ts | 58 ++++++ libs/key-management/src/models/kdf-config.ts | 2 + .../src/cipher-form/cipher-form.stories.ts | 3 +- .../custom-fields.component.spec.ts | 2 + .../login-details-section.component.spec.ts | 2 + .../login-credentials-view.component.spec.ts | 4 + .../login-credentials-view.component.ts | 2 + .../view-identity-sections.component.spec.ts | 2 + .../password-history-view.component.spec.ts | 2 + .../password-reprompt.service.spec.ts | 2 + 89 files changed, 406 insertions(+), 153 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c606b8f933b..c10904c8670 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -141,8 +141,7 @@ } ] } - ], - "no-restricted-imports": ["error", { "patterns": ["src/**/*"] }] + ] } }, { @@ -164,147 +163,6 @@ "tailwindcss/no-contradicting-classname": "error" } }, - { - "files": ["libs/admin-console/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/admin-console/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/angular/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/angular/*", "src/**/*"] }] - } - }, - { - "files": ["libs/auth/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/auth/*", "src/**/*"] }] - } - }, - { - "files": ["libs/billing/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/billing/*", "src/**/*"] }] - } - }, - { - "files": ["libs/common/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/common/*", "src/**/*"] }] - } - }, - { - "files": ["libs/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/components/*", "src/**/*", "@bitwarden/angular/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-components/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/history/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-history/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/legacy/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-legacy/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/navigation/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-navigation/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-ui/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/importer/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/importer/*", "src/**/*"] }] - } - }, - { - "files": ["libs/node/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }] - } - }, - { - "files": ["libs/platform/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/platform/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/send/send-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/send-ui/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/card/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/tools-card/*", "src/**/*"] }] - } - }, - { - "files": ["libs/vault/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/vault/*", "src/**/*"] }] - } - }, { "files": ["apps/browser/src/**/*.ts", "libs/**/*.ts"], "excludedFiles": [ @@ -344,10 +202,30 @@ ] } }, + { + "files": ["**/src/**/*.ts"], + "excludedFiles": ["**/platform/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + "**/platform/**/internal", // General internal pattern + // All features that have been converted to barrel files + "**/platform/messaging/**", + "**/src/**/*" // Prevent relative imports across libs. + ] + } + ] + } + }, { "files": ["bitwarden_license/bit-common/src/**/*.ts"], "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/bit-common/*", "src/**/*"] }] + "no-restricted-imports": [ + "error", + { "patterns": ["@bitwarden/bit-common/*", "**/src/**/*"] } + ] } }, { @@ -357,7 +235,12 @@ "no-restricted-imports": [ "error", { - "patterns": ["biwarden_license/**", "@bitwarden/bit-common/*", "@bitwarden/bit-web/*"] + "patterns": [ + "biwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + "**/src/**/*" + ] } ], // Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded 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 ad7e6f67361..841eefda0ad 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 @@ -25,6 +25,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { UserId } from "@bitwarden/common/types/guid"; import { ButtonModule, I18nMockService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon"; import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; import { AccountSwitcherService } from "../account-switching/services/account-switcher.service"; diff --git a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts index 0917b2703cf..53aedc7a5f3 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts @@ -13,11 +13,23 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; diff --git a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts index b6211bba05f..723152adfab 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts @@ -6,12 +6,26 @@ import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { TwoFactorAuthEmailComponent as TwoFactorAuthEmailBaseComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-email.component"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DialogService } from "../../../../../libs/components/src/dialog"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; diff --git a/apps/browser/src/auth/popup/two-factor-auth.component.ts b/apps/browser/src/auth/popup/two-factor-auth.component.ts index 3cb82118597..f22bbbe202c 100644 --- a/apps/browser/src/auth/popup/two-factor-auth.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth.component.ts @@ -37,6 +37,8 @@ import { ToastService, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction, LoginEmailServiceAbstraction, diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts index b260bfef632..24b63d6b9eb 100644 --- a/apps/browser/src/autofill/content/notification-bar.ts +++ b/apps/browser/src/autofill/content/notification-bar.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ServerConfig } from "../../../../../libs/common/src/platform/abstractions/config/server-config"; import { AddLoginMessageData, diff --git a/apps/browser/src/autofill/types/index.ts b/apps/browser/src/autofill/types/index.ts index a14ef1330cc..606b36e1fb4 100644 --- a/apps/browser/src/autofill/types/index.ts +++ b/apps/browser/src/autofill/types/index.ts @@ -1,4 +1,6 @@ import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum"; import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; import { CipherType } from "@bitwarden/common/vault/enums"; diff --git a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts b/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts index 6270b3503b5..0d49595d2eb 100644 --- a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts +++ b/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/src/abstractions/vault-timeout/vault-timeout.service"; -import { MessagingService } from "@bitwarden/common/src/platform/abstractions/messaging.service"; -import { UserId } from "@bitwarden/common/src/types/guid"; +import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { UserId } from "@bitwarden/common/types/guid"; export class ForegroundVaultTimeoutService implements BaseVaultTimeoutService { constructor(protected messagingService: MessagingService) {} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts index 8ec04045e6c..3b9dc9a1647 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts @@ -8,6 +8,8 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DisclosureTriggerForDirective, IconButtonModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DisclosureComponent } from "../../../../../../../../libs/components/src/disclosure/disclosure.component"; import { runInsideAngular } from "../../../../../platform/browser/run-inside-angular.operator"; import { VaultPopupListFiltersService } from "../../../../../vault/popup/services/vault-popup-list-filters.service"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index c8f590ced57..67e069d388a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -12,6 +12,8 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PasswordHistoryViewComponent } from "../../../../../../../../libs/vault/src/components/password-history-view/password-history-view.component"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 55b59c087c5..6532fb004cb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -37,7 +37,11 @@ import { } from "@bitwarden/components"; import { CopyCipherFieldService } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PremiumUpgradePromptService } from "../../../../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index 25a7d7594ae..ae8d4666651 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -20,6 +20,8 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { ToastService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { AutoFillOptions, diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index e4863d5ee43..586e9182819 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -26,6 +26,8 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view import { ToastService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { AutofillService, diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index ffa09aeb554..528aae111cc 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -15,6 +15,8 @@ import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts index 7f300a508a6..3aab9f935e4 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -16,8 +16,14 @@ import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { BadgeModule, CheckboxModule, Option } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CardComponent } from "../../../../../../libs/components/src/card/card.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../../libs/components/src/form-field/form-field.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SelectModule } from "../../../../../../libs/components/src/select/select.module"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupCompactModeService } from "../../../platform/popup/layout/popup-compact-mode.service"; diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index b1db949f2ee..8abc3f906c0 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -15,8 +15,14 @@ import { } from "@bitwarden/components"; import { VaultIcons } from "@bitwarden/vault"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ItemGroupComponent } from "../../../../../../libs/components/src/item/item-group.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ItemModule } from "../../../../../../libs/components/src/item/item.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NoItemsModule } from "../../../../../../libs/components/src/no-items/no-items.module"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts index 2f8e61c68a4..8d2d734677a 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts @@ -7,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts index 4f17bb5658a..93598bf9eef 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts @@ -7,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index ee17e672a79..8513363956e 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -4,7 +4,9 @@ import { homedir } from "os"; import * as NodeIPC from "node-ipc"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import Deferred from "./deferred"; diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index cd84504c630..94fdde026b2 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -9,13 +9,21 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; +// eslint-disable-next-line no-restricted-imports import { DecryptedCommandData } from "../../src/models/native-messaging/decrypted-command-data"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessage } from "../../src/models/native-messaging/encrypted-message"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessageResponse } from "../../src/models/native-messaging/encrypted-message-response"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessage } from "../../src/models/native-messaging/unencrypted-message"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import IPCService, { IPCOptions } from "./ipc.service"; diff --git a/apps/desktop/src/auth/two-factor-auth-duo.component.ts b/apps/desktop/src/auth/two-factor-auth-duo.component.ts index 72137dc5364..c238b753b64 100644 --- a/apps/desktop/src/auth/two-factor-auth-duo.component.ts +++ b/apps/desktop/src/auth/two-factor-auth-duo.component.ts @@ -21,6 +21,8 @@ import { TypographyModule, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component"; const BroadcasterSubscriptionId = "TwoFactorComponent"; diff --git a/apps/desktop/src/auth/two-factor-auth.component.ts b/apps/desktop/src/auth/two-factor-auth.component.ts index 29271b565c1..9e0898c39e2 100644 --- a/apps/desktop/src/auth/two-factor-auth.component.ts +++ b/apps/desktop/src/auth/two-factor-auth.component.ts @@ -4,19 +4,47 @@ import { Component } from "@angular/core"; import { ReactiveFormsModule } from "@angular/forms"; import { RouterLink } from "@angular/router"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthEmailComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthWebAuthnComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthYubikeyComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { JslibModule } from "../../../../libs/angular/src/jslib.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CheckboxModule } from "../../../../libs/components/src/checkbox"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../libs/components/src/typography"; import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index 62200962dca..0443034f551 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -10,8 +10,8 @@ import { DialogModule, FormFieldModule, IconButtonModule, + DialogService, } from "@bitwarden/components"; -import { DialogService } from "@bitwarden/components/src/dialog"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; export interface ApproveSshRequestParams { diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts index dc6a6d8e906..f8ce50a8490 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -4,6 +4,8 @@ import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts index f5dff801f27..c3cbd00ad38 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts @@ -7,6 +7,8 @@ import { MessageResponse } from "@bitwarden/cli/models/response/message.response import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts index 58c1c576433..1036602b0f8 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -4,6 +4,8 @@ import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts index b9c09a0d671..b410bbec21e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts @@ -2,6 +2,8 @@ import { NgModule } from "@angular/core"; import { BannerModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OnboardingModule } from "../../../../../../apps/web/src/app/shared/components/onboarding/onboarding.module"; import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 631f6409748..8447127fc91 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -9,6 +9,8 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RouterService } from "../../../../../../../apps/web/src/app/core/router.service"; import { ProjectView } from "../../models/view/project.view"; import { ProjectService } from "../project.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts index ebd00a83ea5..ef29f6be7ea 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -9,6 +9,8 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RouterService } from "../../../../../../../../clients/apps/web/src/app/core/router.service"; import { ServiceAccountView } from "../../models/view/service-account.view"; import { ServiceAccountService } from "../service-account.service"; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index f5940b8e144..93d25af1a53 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -301,6 +301,8 @@ import { IndividualVaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NewDeviceVerificationNoticeService } from "../../../vault/src/services/new-device-verification-notice.service"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service"; import { ViewCacheService } from "../platform/abstractions/view-cache.service"; diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts index e278113a653..ba19cf808ee 100644 --- a/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts @@ -9,6 +9,8 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; import { VaultProfileService } from "../services/vault-profile.service"; diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts index 20550e0e8cf..8b406877a12 100644 --- a/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts @@ -9,6 +9,8 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; import { VaultProfileService } from "../services/vault-profile.service"; diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index dd0b49f356a..260780e1964 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -22,6 +22,8 @@ import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../abstractions/deprecated-vault-filter.service"; import { DynamicTreeNode } from "../models/dynamic-tree-node.model"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { COLLAPSED_GROUPINGS } from "./../../../../../common/src/vault/services/key-state/collapsed-groupings.state"; const NestingDelimiter = "/"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index b07504b7c8d..9f504c75d29 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -18,7 +18,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { LockIcon } from "../icons"; import { RegistrationCheckEmailIcon } from "../icons/registration-check-email.icon"; 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 91229f38ab2..05ddb9614f1 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -9,8 +9,14 @@ import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IconModule, Icon } from "../../../../components/src/icon"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../components/src/typography"; import { BitwardenLogo, BitwardenShield } from "../icons"; 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 27eb27c53b9..c7e15d9dcfa 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts @@ -7,7 +7,11 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "../../../../components/src/utils/i18n-mock.service"; import { LockIcon } from "../icons"; diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index 94baecb9ef2..c613cf5f533 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -26,7 +26,11 @@ import { } from "@bitwarden/components"; import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InputsFieldMatch } from "../../../../angular/src/auth/validators/inputs-field-match.validator"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; import { PasswordCalloutComponent } from "../password-callout/password-callout.component"; diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 99c0aba81b8..41577328f87 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -14,7 +14,8 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { InputPasswordComponent } from "./input-password.component"; diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts index fa3ad2ae2b9..6047cc3d27a 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts @@ -28,7 +28,8 @@ import { } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests"; import { LoginEmailService } from "../../../common"; import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts index 0557227938f..b54529f6a2c 100644 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts +++ b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts @@ -15,6 +15,8 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ToastService } from "../../../../components/src/toast"; import { InputPasswordComponent } from "../input-password/input-password.component"; import { PasswordInputResult } from "../input-password/password-input-result"; diff --git a/libs/auth/src/common/models/domain/user-decryption-options.ts b/libs/auth/src/common/models/domain/user-decryption-options.ts index 95efe2b0077..00b78064d83 100644 --- a/libs/auth/src/common/models/domain/user-decryption-options.ts +++ b/libs/auth/src/common/models/domain/user-decryption-options.ts @@ -4,6 +4,8 @@ import { Jsonify } from "type-fest"; import { KeyConnectorUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/key-connector-user-decryption-option.response"; import { TrustedDeviceUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IdentityTokenResponse } from "@bitwarden/common/src/auth/models/response/identity-token.response"; /** diff --git a/libs/auth/src/common/services/login-email/login-email.service.ts b/libs/auth/src/common/services/login-email/login-email.service.ts index feb0cba1bdc..aa13afd5004 100644 --- a/libs/auth/src/common/services/login-email/login-email.service.ts +++ b/libs/auth/src/common/services/login-email/login-email.service.ts @@ -6,6 +6,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { GlobalState, KeyDefinition, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 1f98a117c88..57a653b205e 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -35,6 +35,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { MasterKey } from "@bitwarden/common/types/key"; diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts index ffb660e6a7f..7c44a6f1682 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts @@ -8,6 +8,8 @@ import { USER_DECRYPTION_OPTIONS_DISK, UserKeyDefinition, } from "@bitwarden/common/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction"; diff --git a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts index 791c4688078..588f75bde49 100644 --- a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts +++ b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet } from "../../../../../auth/src/common/models"; export class WebauthnRotateCredentialRequest { diff --git a/libs/common/src/auth/services/anonymous-hub.service.ts b/libs/common/src/auth/services/anonymous-hub.service.ts index a268c8a2712..3900dd53ee0 100644 --- a/libs/common/src/auth/services/anonymous-hub.service.ts +++ b/libs/common/src/auth/services/anonymous-hub.service.ts @@ -9,6 +9,8 @@ import { import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; import { firstValueFrom } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "../../../../auth/src/common/abstractions"; import { NotificationType } from "../../enums"; import { diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts index 5663384714d..4b7b8a2b262 100644 --- a/libs/common/src/auth/services/auth.service.spec.ts +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index bab83fc55db..d855a1be34f 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,6 +11,8 @@ import { switchMap, } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { StateService } from "../../platform/abstractions/state.service"; diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index 409150552ad..c40091a66fa 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -4,6 +4,8 @@ import { firstValueFrom, map, Observable } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { AppIdService } from "../../platform/abstractions/app-id.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; diff --git a/libs/common/src/auth/services/device-trust.service.spec.ts b/libs/common/src/auth/services/device-trust.service.spec.ts index 66a91a693e5..943653e3129 100644 --- a/libs/common/src/auth/services/device-trust.service.spec.ts +++ b/libs/common/src/auth/services/device-trust.service.spec.ts @@ -3,7 +3,11 @@ import { BehaviorSubject, of } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptions } from "../../../../auth/src/common/models/domain/user-decryption-options"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { FakeActiveUserState } from "../../../spec/fake-state"; diff --git a/libs/common/src/auth/services/key-connector.service.spec.ts b/libs/common/src/auth/services/key-connector.service.spec.ts index b1bf87693c1..660f1124f4a 100644 --- a/libs/common/src/auth/services/key-connector.service.spec.ts +++ b/libs/common/src/auth/services/key-connector.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index ed622b21c86..95b6f3f8b9d 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -4,7 +4,11 @@ import { BehaviorSubject } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "../../../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index c5451ce30c6..0bdf98cef7b 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -8,6 +8,8 @@ import { } from "@bitwarden/admin-console/common"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { I18nService } from "../../platform/abstractions/i18n.service"; diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index 081dafb1706..102e4bac8da 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -14,6 +14,8 @@ import { KeyService, } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KdfConfigService } from "../../../../../key-management/src/abstractions/kdf-config.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; import { VaultTimeoutSettingsService } from "../../../abstractions/vault-timeout/vault-timeout-settings.service"; diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 2935c1958a4..3f0a0adfcb5 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -10,6 +10,8 @@ import { KeyService, } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin.service.abstraction"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { HashPurpose } from "../../../platform/enums"; diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 8c1d1117c89..f1c3aed6a58 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -6,6 +6,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../../../auth/src/common/abstractions"; import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "../../auth/abstractions/account.service"; diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index d2bf11a20ce..95d17e6d046 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -8,6 +8,8 @@ import { Observable, of, switchMap } from "rxjs"; import { getHostname, parse } from "tldts"; import { Merge } from "type-fest"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; diff --git a/libs/common/src/platform/models/domain/enc-string.spec.ts b/libs/common/src/platform/models/domain/enc-string.spec.ts index 85108a9609b..b4916b9f70a 100644 --- a/libs/common/src/platform/models/domain/enc-string.spec.ts +++ b/libs/common/src/platform/models/domain/enc-string.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeEncString, makeStaticByteArray } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; diff --git a/libs/common/src/platform/services/container.service.ts b/libs/common/src/platform/services/container.service.ts index 6022e097ab0..c3e727a2e1e 100644 --- a/libs/common/src/platform/services/container.service.ts +++ b/libs/common/src/platform/services/container.service.ts @@ -1,3 +1,5 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { EncryptService } from "../abstractions/encrypt.service"; diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts index 23a8ba3138b..16b3968045a 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DefaultKeyService } from "../../../../key-management/src/key.service"; import { CsprngArray } from "../../types/csprng"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts index abb8993c39c..a8947a49f45 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -1,3 +1,5 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { UserId } from "../../types/guid"; import { KeySuffixOptions } from "../enums"; diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 9e36aa69417..138c7c03318 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -8,8 +8,14 @@ import { CollectionDetailsResponse, } from "@bitwarden/admin-console/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction } from "../../../../auth/src/common/abstractions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "../../../../auth/src/common/types"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { InternalOrganizationServiceAbstraction } from "../../admin-console/abstractions/organization/organization.service.abstraction"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts index 540f26bba2d..4168baf1383 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts @@ -10,6 +10,8 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, mockAccountServiceWith, FakeStateProvider } from "../../../spec"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts index 0f5bdca93da..ffc8b6e0144 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts @@ -21,6 +21,8 @@ import { } from "@bitwarden/auth/common"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index b074e7a46ad..8cae7170738 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 509a17a8a0e..64df3204aca 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -3,6 +3,8 @@ import { Jsonify } from "type-fest"; import { UserId } from "@bitwarden/common/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index e6ea762c125..8ab6a2d3d34 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -3,6 +3,8 @@ import { BehaviorSubject, map, of } from "rxjs"; import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CipherDecryptionKeys, KeyService, diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 36db5b87079..12712f169a3 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -18,6 +18,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 612cd83d99b..9fdb4327b98 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeEncString } from "../../../../spec"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 3aac5374fcb..fe77806877d 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -5,6 +5,8 @@ import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index fb94e43b196..7ddb32da281 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -5,6 +5,8 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { delay, of } from "rxjs"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; import { ButtonModule } from "../button"; diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index c322f29b957..f3e5a5cb3f7 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -9,6 +9,8 @@ import { } from "@angular/forms"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; import { BadgeModule } from "../badge"; diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index 5b07d0bbb96..a653d79f83f 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -18,6 +18,8 @@ import { import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { compareValues } from "../../../common/src/platform/misc/compare-values"; import { ButtonModule } from "../button"; import { IconButtonModule } from "../icon-button"; diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index f5ab624bb22..da59c2986b2 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -5,13 +5,29 @@ import { Observable } from "rxjs"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; import { KdfConfig } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-provider-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderResponse } from "../../../common/src/admin-console/models/response/profile-provider.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeySuffixOptions, HashPurpose } from "../../../common/src/platform/enums"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedString, EncString } from "../../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SymmetricCryptoKey } from "../../../common/src/platform/models/domain/symmetric-crypto-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationId, UserId } from "../../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserKey, MasterKey, diff --git a/libs/key-management/src/biometrics/biometric-state.service.ts b/libs/key-management/src/biometrics/biometric-state.service.ts index c7f958c97a8..ae6e52ce632 100644 --- a/libs/key-management/src/biometrics/biometric-state.service.ts +++ b/libs/key-management/src/biometrics/biometric-state.service.ts @@ -2,8 +2,14 @@ // @ts-strict-ignore import { Observable, firstValueFrom, map, combineLatest } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedString, EncString } from "../../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ActiveUserState, GlobalState, StateProvider } from "../../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "../../../common/src/types/guid"; import { diff --git a/libs/key-management/src/biometrics/biometric.state.ts b/libs/key-management/src/biometrics/biometric.state.ts index c37b7d7370d..e5ea4fbae3b 100644 --- a/libs/key-management/src/biometrics/biometric.state.ts +++ b/libs/key-management/src/biometrics/biometric.state.ts @@ -1,9 +1,15 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedString } from "../../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyDefinition, BIOMETRIC_SETTINGS_DISK, UserKeyDefinition, } from "../../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "../../../common/src/types/guid"; /** diff --git a/libs/key-management/src/kdf-config.service.spec.ts b/libs/key-management/src/kdf-config.service.spec.ts index c9912930e2c..76a13fe10fc 100644 --- a/libs/key-management/src/kdf-config.service.spec.ts +++ b/libs/key-management/src/kdf-config.service.spec.ts @@ -3,8 +3,12 @@ import { FakeStateProvider, mockAccountServiceWith, } from "@bitwarden/common/spec"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Utils } from "../../common/src/platform/misc/utils"; import { DefaultKdfConfigService } from "./kdf-config.service"; diff --git a/libs/key-management/src/kdf-config.service.ts b/libs/key-management/src/kdf-config.service.ts index 884a94fb95f..f0c964c5d2e 100644 --- a/libs/key-management/src/kdf-config.service.ts +++ b/libs/key-management/src/kdf-config.service.ts @@ -2,8 +2,12 @@ // @ts-strict-ignore import { firstValueFrom, Observable } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KDF_CONFIG_DISK, StateProvider, UserKeyDefinition } from "../../common/src/platform/state"; import { KdfConfigService } from "./abstractions/kdf-config.service"; diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index b77c14ff532..62cdce230ea 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -3,6 +3,8 @@ import { bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs" import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../auth/src/common/abstractions"; import { awaitAsync, @@ -13,29 +15,69 @@ import { import { FakeAccountService, mockAccountServiceWith } from "../../common/spec/fake-account-service"; import { FakeActiveUserState, FakeSingleUserState } from "../../common/spec/fake-state"; import { FakeStateProvider } from "../../common/spec/fake-state-provider"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FakeMasterPasswordService } from "../../common/src/auth/services/master-password/fake-master-password.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptService } from "../../common/src/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogService } from "../../common/src/platform/abstractions/log.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { StateService } from "../../common/src/platform/abstractions/state.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Encrypted } from "../../common/src/platform/interfaces/encrypted"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Utils } from "../../common/src/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PRIVATE_KEY, USER_EVER_HAD_USER_KEY, USER_KEY, } from "../../common/src/platform/services/key-state/user-key.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserKeyDefinition } from "../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CsprngArray } from "../../common/src/types/csprng"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationId, UserId } from "../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserKey, MasterKey } from "../../common/src/types/key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type"; import { KdfConfigService } from "./abstractions/kdf-config.service"; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index b1debccb95d..5425dbc53f9 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -12,37 +12,93 @@ import { switchMap, } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../auth/src/common/abstractions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedOrganizationKeyData } from "../../common/src/admin-console/models/data/encrypted-organization-key.data"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BaseEncryptedOrganizationKey } from "../../common/src/admin-console/models/domain/encrypted-organization-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileOrganizationResponse } from "../../common/src/admin-console/models/response/profile-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderOrganizationResponse } from "../../common/src/admin-console/models/response/profile-provider-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderResponse } from "../../common/src/admin-console/models/response/profile-provider.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AccountService } from "../../common/src/auth/abstractions/account.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InternalMasterPasswordServiceAbstraction } from "../../common/src/auth/abstractions/master-password.service.abstraction"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptService } from "../../common/src/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogService } from "../../common/src/platform/abstractions/log.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { StateService } from "../../common/src/platform/abstractions/state.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeySuffixOptions, HashPurpose } from "../../common/src/platform/enums"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { convertValues } from "../../common/src/platform/misc/convert-values"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Utils } from "../../common/src/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EFFLongWordList } from "../../common/src/platform/misc/wordlist"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PRIVATE_KEY, USER_EVER_HAD_USER_KEY, USER_KEY, } from "../../common/src/platform/services/key-state/user-key.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ActiveUserState, StateProvider } from "../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CsprngArray } from "../../common/src/types/csprng"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationId, ProviderId, UserId } from "../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrgKey, UserKey, @@ -52,6 +108,8 @@ import { UserPrivateKey, UserPublicKey, } from "../../common/src/types/key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type"; import { KdfConfigService } from "./abstractions/kdf-config.service"; diff --git a/libs/key-management/src/models/kdf-config.ts b/libs/key-management/src/models/kdf-config.ts index 11431337a39..fe9c0de255c 100644 --- a/libs/key-management/src/models/kdf-config.ts +++ b/libs/key-management/src/models/kdf-config.ts @@ -1,5 +1,7 @@ import { Jsonify } from "type-fest"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RangeWithDefault } from "../../../common/src/platform/misc/range-with-default"; import { KdfType } from "../enums/kdf-type.enum"; diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 72c4acb23cd..f7146776b73 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -31,7 +31,8 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests"; import { CipherFormService } from "./abstractions/cipher-form.service"; diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts index 0c0fa1b4184..a5e57d2858f 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts @@ -19,6 +19,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; import { DialogService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "../../../../../components/src/form-field/password-input-toggle.directive"; import { CipherFormConfig } from "../../abstractions/cipher-form-config.service"; import { CipherFormContainer } from "../../cipher-form-container"; diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts index 182427f7f42..69cfd419742 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts @@ -14,6 +14,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "@bitwarden/components/src/form-field/password-input-toggle.directive"; import { CipherFormGenerationService } from "../../abstractions/cipher-form-generation.service"; diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts index 9c09028650e..9bb96d1accd 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts @@ -18,7 +18,11 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { BitFormFieldComponent, ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "@bitwarden/components/src/form-field/password-input-toggle.directive"; import { LoginCredentialsViewComponent } from "./login-credentials-view.component"; diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index b24fcdfa1fd..7533ac26471 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -21,6 +21,8 @@ import { ColorPasswordModule, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PremiumUpgradePromptService } from "../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service"; import { BitTotpCountdownComponent } from "../../components/totp-countdown/totp-countdown.component"; import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts index 4c84b2852a8..21f82234b2c 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts @@ -8,6 +8,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; import { SectionHeaderComponent } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitInputDirective } from "../../../../components/src/input/input.directive"; import { ViewIdentitySectionsComponent } from "./view-identity-sections.component"; diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts b/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts index 461cc734d76..fb90df4d6ac 100644 --- a/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts +++ b/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts @@ -10,6 +10,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ColorPasswordModule, ItemModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component"; import { PasswordHistoryViewComponent } from "./password-history-view.component"; diff --git a/libs/vault/src/services/password-reprompt.service.spec.ts b/libs/vault/src/services/password-reprompt.service.spec.ts index 300b98215c2..7d0a834b2d6 100644 --- a/libs/vault/src/services/password-reprompt.service.spec.ts +++ b/libs/vault/src/services/password-reprompt.service.spec.ts @@ -1,5 +1,7 @@ import { MockProxy, mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserVerificationService } from "@bitwarden/common/src/auth/abstractions/user-verification/user-verification.service.abstraction"; import { DialogService } from "@bitwarden/components"; From c0dfac4495fc96c0f1333c6a8066f1c5fdbe75c2 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:11:15 +0100 Subject: [PATCH 132/270] Autosync the updated translations (#12794) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 56 ++ apps/browser/src/_locales/az/messages.json | 56 ++ apps/browser/src/_locales/be/messages.json | 56 ++ apps/browser/src/_locales/bg/messages.json | 56 ++ apps/browser/src/_locales/bn/messages.json | 56 ++ apps/browser/src/_locales/bs/messages.json | 56 ++ apps/browser/src/_locales/ca/messages.json | 56 ++ apps/browser/src/_locales/cs/messages.json | 56 ++ apps/browser/src/_locales/cy/messages.json | 56 ++ apps/browser/src/_locales/da/messages.json | 56 ++ apps/browser/src/_locales/de/messages.json | 56 ++ apps/browser/src/_locales/el/messages.json | 58 +- apps/browser/src/_locales/en_GB/messages.json | 56 ++ apps/browser/src/_locales/en_IN/messages.json | 56 ++ apps/browser/src/_locales/es/messages.json | 56 ++ apps/browser/src/_locales/et/messages.json | 56 ++ apps/browser/src/_locales/eu/messages.json | 56 ++ apps/browser/src/_locales/fa/messages.json | 56 ++ apps/browser/src/_locales/fi/messages.json | 56 ++ apps/browser/src/_locales/fil/messages.json | 56 ++ apps/browser/src/_locales/fr/messages.json | 56 ++ apps/browser/src/_locales/gl/messages.json | 56 ++ apps/browser/src/_locales/he/messages.json | 56 ++ apps/browser/src/_locales/hi/messages.json | 56 ++ apps/browser/src/_locales/hr/messages.json | 56 ++ apps/browser/src/_locales/hu/messages.json | 56 ++ apps/browser/src/_locales/id/messages.json | 56 ++ apps/browser/src/_locales/it/messages.json | 56 ++ apps/browser/src/_locales/ja/messages.json | 56 ++ apps/browser/src/_locales/ka/messages.json | 56 ++ apps/browser/src/_locales/km/messages.json | 56 ++ apps/browser/src/_locales/kn/messages.json | 56 ++ apps/browser/src/_locales/ko/messages.json | 56 ++ apps/browser/src/_locales/lt/messages.json | 56 ++ apps/browser/src/_locales/lv/messages.json | 56 ++ apps/browser/src/_locales/ml/messages.json | 56 ++ apps/browser/src/_locales/mr/messages.json | 56 ++ apps/browser/src/_locales/my/messages.json | 56 ++ apps/browser/src/_locales/nb/messages.json | 56 ++ apps/browser/src/_locales/ne/messages.json | 56 ++ apps/browser/src/_locales/nl/messages.json | 56 ++ apps/browser/src/_locales/nn/messages.json | 56 ++ apps/browser/src/_locales/or/messages.json | 56 ++ apps/browser/src/_locales/pl/messages.json | 56 ++ apps/browser/src/_locales/pt_BR/messages.json | 56 ++ apps/browser/src/_locales/pt_PT/messages.json | 56 ++ apps/browser/src/_locales/ro/messages.json | 56 ++ apps/browser/src/_locales/ru/messages.json | 56 ++ apps/browser/src/_locales/si/messages.json | 56 ++ apps/browser/src/_locales/sk/messages.json | 56 ++ apps/browser/src/_locales/sl/messages.json | 56 ++ apps/browser/src/_locales/sr/messages.json | 56 ++ apps/browser/src/_locales/sv/messages.json | 56 ++ apps/browser/src/_locales/te/messages.json | 56 ++ apps/browser/src/_locales/th/messages.json | 56 ++ apps/browser/src/_locales/tr/messages.json | 56 ++ apps/browser/src/_locales/uk/messages.json | 56 ++ apps/browser/src/_locales/vi/messages.json | 56 ++ apps/browser/src/_locales/zh_CN/messages.json | 82 +- apps/browser/src/_locales/zh_TW/messages.json | 746 ++++++++++-------- apps/browser/store/locales/zh_CN/copy.resx | 4 +- 61 files changed, 3721 insertions(+), 361 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 46f119bdafe..8b4bbe23e04 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2324,6 +2324,9 @@ "message": "النطاقات", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "النطاقات المستبعدة" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden لن يطلب حفظ تفاصيل تسجيل الدخول لهذه النطافات لجميع الحسابات مسجلة الدخول. يجب عليك تحديث الصفحة لكي تصبح التغييرات نافذة المفعول." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "الموقع $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "تم حفظ تغييرات استبعاد النطاقات" }, @@ -2789,6 +2804,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "إنشاء اسم المستخدم" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 51bfe95a48a..86f1d07fb3f 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2324,6 +2324,9 @@ "message": "Domenlər", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Əngəllənmiş domenlər" + }, "excludedDomains": { "message": "İstisna edilən domenlər" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, giriş etmiş bütün hesablar üçün bu domenlərin giriş detallarını saxlamağı soruşmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." }, + "blockedDomainsDesc": { + "message": "Bu veb saytlar üçün avto-doldurma və digər əlaqəli özəlliklər təklif olunmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." + }, + "autofillBlockedNotice": { + "message": "Bu veb sayt üçün avto-doldurma əngəllənib. Bunu ayarlarda incələyin və ya dəyişdirin." + }, + "autofillBlockedTooltip": { + "message": "Bu veb saytda avto-doldurma əngəllənib. Ayarlarda incələyin." + }, "websiteItemLabel": { "message": "Veb sayt $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Əngəllənmiş domen dəyişiklikləri saxlanıldı" + }, "excludedDomainsSavedSuccess": { "message": "İstisna domen dəyişikliyi saxlanıldı" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "İstifadəçi adı yarat" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Bu elementə düzəliş etmə icazəniz yoxdur" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Bitwarden masaüstü tətbiqi bağlı olduğu üçün biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, "authenticating": { "message": "Kimlik doğrulama" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 46c804d27d9..036b1dfcb24 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2324,6 +2324,9 @@ "message": "Дамены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Выключаныя дамены" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Вэб-сайт $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генерыраваць імя карыстальніка" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 7cbbdc07be4..098e2b91051 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2324,6 +2324,9 @@ "message": "Домейни", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Блокирани домейни" + }, "excludedDomains": { "message": "Изключени домейни" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Битуорден няма да пита дали да запазва данните за вход в тези сайтове за всички регистрации, в които сте вписан(а). За да влезе правилото в сила, презаредете страницата." }, + "blockedDomainsDesc": { + "message": "Автоматичното попълване и други свързани функции няма да бъдат предлагани за тези уеб сайтове. Трябва да презаредите страницата, за да влязат в сила промените." + }, + "autofillBlockedNotice": { + "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате и промените това в настройките." + }, + "autofillBlockedTooltip": { + "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате това в настройките." + }, "websiteItemLabel": { "message": "Уеб сайт $number$ (адрес)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Промените на блокираните домейни са запазени" + }, "excludedDomainsSavedSuccess": { "message": "Промените на изключените домейни са запазени" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генериране на потр. име" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Нямате право за редактиране на този елемент" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Отключването с биометрични данни не е налично, тъй като приложението на Биуорден за компютър не работи." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, "authenticating": { "message": "Удостоверяване" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 2da7b40554c..f60fc2c9683 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 9f5a610c351..aaf04da98b7 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index b38783abc82..e7113d5aab1 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2324,6 +2324,9 @@ "message": "Dominis", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominis exclosos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no demanarà que es guarden les dades d'inici de sessió d'aquests dominis per a tots els comptes iniciats. Heu d'actualitzar la pàgina perquè els canvis tinguen efecte." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genera un nom d'usuari" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index bcc7020ffb3..eb2f9149daf 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2324,6 +2324,9 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, "excludedDomains": { "message": "Vyloučené domény" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude žádat o uložení přihlašovacích údajů pro tyto domény pro všechny přihlášené účty. Aby se změny projevily, musíte stránku obnovit." }, + "blockedDomainsDesc": { + "message": "Automatické vyplňování a další související funkce nebudou pro tyto webové stránky nabízeny. Aby se změny projevily, musíte stránku aktualizovat." + }, + "autofillBlockedNotice": { + "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to nebo to změňte v nastavení." + }, + "autofillBlockedTooltip": { + "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to v nastavení." + }, "websiteItemLabel": { "message": "Webová stránka $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Změny v zablokovaných doménách byly uloženy" + }, "excludedDomainsSavedSuccess": { "message": "Vyloučené změny domény byly uloženy" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Vygenerovat uživatelské jméno" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nemáte oprávnění upravit tuto položku" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrické odemknutí není dostupné, protože je aplikace Bitwarden zavřena." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrické odemknutí není dostupné, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, "authenticating": { "message": "Ověřování" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 3bc85b10fb5..8ad494f0bfb 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Parthau wedi'u heithrio" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Gwall" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Cynhyrchu enw defnyddiwr" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 433cdebbb17..9008049e1a4 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2324,6 +2324,9 @@ "message": "Domæner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokerede domæner" + }, "excludedDomains": { "message": "Ekskluderede domæner" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden vil ikke anmode om at gemme login-detaljer for disse domæner for alle indloggede konti. Siden skal opfriskes for at effektuere ændringerne." }, + "blockedDomainsDesc": { + "message": "Autofyldning og andre relaterede funktioner tilbydes ikke på disse websteder. Siden skal opdateres for at effektuere ændringerne." + }, + "autofillBlockedNotice": { + "message": "Autoudfyldning er blokeret på dette websted. Gennemgå eller ændr dette i Indstillinger." + }, + "autofillBlockedTooltip": { + "message": "Autoudfyldning er blokeret på dette websted. Gennemgå i Indstillinger." + }, "websiteItemLabel": { "message": "Websted $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blokeret domæne-ændringer gemt" + }, "excludedDomainsSavedSuccess": { "message": "Ekskluderet domæne-ændringer gemt" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generér brugernavn" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Ingen tilladelse til at redigere dette emne" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisk oplåsning er utilgængelig, da Bitwarden-appen er lukket." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, "authenticating": { "message": "Godkender" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 2fc5297205a..bc1158bfc21 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Ausgeschlossene Domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden wird für alle angemeldeten Konten nicht danach fragen Zugangsdaten für diese Domains speichern. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, + "blockedDomainsDesc": { + "message": "Autofill und andere zugehörige Funktionen werden für diese Websites nicht angeboten. Sie müssen die Seite aktualisieren, damit die Änderungen wirksam werden." + }, + "autofillBlockedNotice": { + "message": "Das automatische Ausfüllen ist für diese Website gesperrt. Dieses Verhalten kann in den Einstellungen überprüft oder geändert werden." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Änderungen der ausgeschlossenen Domain gespeichert" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte den unten aufgelisteten Tresoreintrag bzw. die Tresoreinträge nicht entschlüsseln." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Benutzername generieren" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authentifizierung" }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index a33bc4d0e20..bdb0eb2c17d 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1005,7 +1005,7 @@ "message": "Λίστα στοιχείων ταυτότητας στη σελίδα Καρτέλας για εύκολη αυτόματη συμπλήρωση." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Κάντε κλικ στα αντικείμενα για αυτόματη συμπλήρωση στην προβολή Θησαυ/κίου" }, "clearClipboard": { "message": "Εκκαθάριση Πρόχειρου", @@ -2324,6 +2324,9 @@ "message": "Τομείς", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Αποκλεισμένοι τομείς" + }, "excludedDomains": { "message": "Εξαιρούμενοι Τομείς" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Το Bitwarden δε θα ρωτήσει για να αποθηκεύσετε τα στοιχεία σύνδεσης για αυτούς τους τομείς, για όλους τους συνδεδεμένους λογαριασμούς. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." }, + "blockedDomainsDesc": { + "message": "Η αυτόματη συμπλήρωση και άλλες σχετικές λειτουργίες δεν θα προσφερθούν για αυτούς τους ιστότοπους. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Ιστοσελίδα $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Οι αλλαγές αποκλεισμένων τομέων αποθηκεύτηκαν" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Σφάλμα αποκρυπτογράφησης" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Το Bitwarden δεν μπόρεσε να αποκρυπτογραφήσει τα αντικείμενα θησαυ/κίου που αναφέρονται παρακάτω." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Δημιουργία ονόματος χρήστη" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Δεν έχετε δικαίωμα να επεξεργαστείτε αυτό το αντικείμενο" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή απαιτείται πρώτα το ξεκλείδωμα με PIN ή κωδικό πρόσβασης." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο προς το παρόν." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή η εφαρμογή Bitwarden επιφάνειας εργασίας είναι κλειστή." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Ταυτοποίηση" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 2c13f1b9259..7e938bb6b2c 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index b6031381d2d..70e725c3a88 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded Domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate Username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index d26716dd909..d9cd2517816 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2324,6 +2324,9 @@ "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominios excluidos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no pedirá que se guarden los datos de acceso para estos dominios en todas las sesiones iniciadas. Debe actualizar la página para que los cambios surtan efecto." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generar nombre de usuario" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "No tiene permiso de editar este elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 069135ddb08..5245de4fb7e 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Väljajäetud domeenid" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genereeri kasutajanimi" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 47a56583c64..fc875be6da0 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Kanporatutako domeinuak" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Sortu erabiltzaile izena" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 2b8a5e20da1..d2362a2655f 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "دامنه های مستثنی" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "ایجاد نام کاربری" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 718c0631156..0dd96ef9a02 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2324,6 +2324,9 @@ "message": "Verkkotunnukset", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Ohitettavat verkkotunnukset" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ei pyydä kirjautumistietojen tallennusta näillä verkkotunnuksilla. Koskee kaikkia kirjautuneita tilejä. Ota muutokset käyttöön päivittämällä sivu." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Verkkotunnus $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Rajoitettujen verkkotunnusten muutokset tallennettiin" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Luo käyttäjätunnus" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Sinulla ei ole oikeutta muokata tätä kohdetta" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Todennetaan" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index a504cbd3174..d0fdf1018fb 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Inilayo na Domain" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Lumikha ng username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 9566f457861..913391d218c 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -2324,6 +2324,9 @@ "message": "Domaines", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domaines bloqués" + }, "excludedDomains": { "message": "Domaines exclus" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ne demandera pas d'enregistrer les détails de connexion pour ces domaines pour tous les comptes connectés. Vous devez actualiser la page pour que les modifications prennent effet." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Site web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Changements de domaines exclus enregistrés" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Générer un nom d'utilisateur" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Vous n'êtes pas autorisé à modifier cet élément" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authentification" }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index b3eaae64486..e655159f246 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2324,6 +2324,9 @@ "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominios excluídos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden non ofrecerá gardar contas para estes dominios en ningunha das sesións iniciadas. Recarga a páxina para que os cambios fornezan efecto." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Dominios excluídos gardados" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Xerar nome de usuario" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Non tes permiso para modificar esta entrada" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autenticando" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 5101c11a653..1a3057ac291 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "שגיאה" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 10ad502be82..fd4a6612af4 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "बहिष्कृत डोमेन" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "एरर" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "उपयोगकर्ता नाम बनाएँ" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index c93922cb913..331bc109309 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2324,6 +2324,9 @@ "message": "Domene", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Izuzete domene" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden neće nuditi spremanje podataka za prijavu za ove domene za sve prijavljene račune. Moraš osvježiti stranicu kako bi promjene stupile na snagu." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web stranica $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Spremljene promjene izuzete domene" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Pogreška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generiraj korisničko ime" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nemaš prava za uređivanje ove stavke" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autentifikacija" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 8b8c9722909..0aea2c7eced 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2324,6 +2324,9 @@ "message": "Tartomány", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Letiltott tartományok" + }, "excludedDomains": { "message": "Kizárt domainek" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "A Bitwarden nem kéri a bejelentkezési adatok mentését ezeknél a tartományoknál az összes bejelentkezési fiókra vonatkozva. A változtatások életbe lépéséhez frissíteni kell az oldalt." }, + "blockedDomainsDesc": { + "message": "Az automatikus kitöltés és az egyéb kapcsolódó funkciók ezeken a webhelyeken nincsenek a kínálatban. A változtatások életbe lépéséhez frissíteni kell az oldalt." + }, + "autofillBlockedNotice": { + "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át vagy módosítsuk ezt a beállításokban." + }, + "autofillBlockedTooltip": { + "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át ezt a beállításokban." + }, "websiteItemLabel": { "message": "Webhely $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "A letiltott tartomány módosítások mentésre kerültek." + }, "excludedDomainsSavedSuccess": { "message": "A kizárt tartomány módosítások mentésre kerültek." }, @@ -2789,6 +2804,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Felhasználónév generálása" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nincs jogosulltság ezen elem szerkesztéséheu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "A biometrikus feloldás nem érhető el, mert a Bitwarden asztali alkalmazás be van zárva." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 7b1ad51e0b7..82776a8e82b 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2324,6 +2324,9 @@ "message": "Domain", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domain yang Dikecualikan" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "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." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Situs web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Perubahan domain yang diabaikan telah disimpan" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Buat nama pengguna baru" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 6490a441832..0653623f078 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2324,6 +2324,9 @@ "message": "Domini", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domini bloccati" + }, "excludedDomains": { "message": "Domini esclusi" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden non chiederà di salvare le credenziali di accesso per questi domini per tutti gli account sul dispositivo. Ricarica la pagina affinché le modifiche abbiano effetto." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Sito $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Modifiche del dominio escluso salvate" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genera nome utente" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Non hai i permessi per modificare questo elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Lo sblocco biometrico non è disponibile perché l'app desktop Bitwarden è chiusa." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, "authenticating": { "message": "Autenticazione" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 42dd73020ec..cc1f34e4985 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2324,6 +2324,9 @@ "message": "ドメイン", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "除外するドメイン" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden はログインしているすべてのアカウントで、これらのドメインのログイン情報を保存するよう要求しません。 変更を有効にするにはページを更新する必要があります。" }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "ウェブサイト $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "除外ドメインの変更を保存しました" }, @@ -2789,6 +2804,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "ユーザー名を生成" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "このアイテムを編集する権限がありません" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "認証中" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 5da73c6755b..50fdc6613c5 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2324,6 +2324,9 @@ "message": "დომენები", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "შეცდომა" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "ავთენტიკაცია" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 09e26c18b5b..3f9e99e5637 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "ಹೊರತುಪಡಿಸಿದ ಡೊಮೇನ್ಗಳು" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index d6da55f600d..4ac6d281b09 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2324,6 +2324,9 @@ "message": "도메인", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "제외된 도메인" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "BItwarden은 로그인한 모든 계정에 대해 이러한 도메인에 대한 로그인 세부 정보를 저장하도록 요청하지 않습니다. 변경 사항을 적용하려면 페이지를 새로 고쳐야 합니다" }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "웹사이트 $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "제외된 도메인 변경 사항 저장됨" }, @@ -2789,6 +2804,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "아이디 생성" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "아이템을 수정할 권한이 없습니다." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "인증 중" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index eaf1cb9f9db..3c81df00f10 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2324,6 +2324,9 @@ "message": "Domenai", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Išskirti domenai" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "„Bitwarden“ neprašys išsaugoti prisijungimo detalių šiems domenams, visose prisijungusiose paskyrose. Turite atnaujinti puslapį, kad pokyčiai pradėtų galioti." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Klaida" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generuoti vartotojo vardą" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 2c2a9c3c69c..fc682ced389 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2324,6 +2324,9 @@ "message": "Domēna vārdi", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Liegtie domēna vārdi" + }, "excludedDomains": { "message": "Izņēmuma domēni" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nevaicās saglabāt pieteikšanās datus visiem šī domēna kontiem, kuri ir pieteikušies. Ir jāpārlādē lapa, lai iedarbotos izmaiņas." }, + "blockedDomainsDesc": { + "message": "Automātiskā aizpilde un citas saistītās iespējas šajās tīmekļvietnēs netiks piedāvātas. Ir jāatsvaidzina lapa, lai izmaiņas iedarbotos." + }, + "autofillBlockedNotice": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo pārskatīt vai mainīt var iestatījumos." + }, + "autofillBlockedTooltip": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo var pārskatīt iestatījumos." + }, "websiteItemLabel": { "message": "Tīmekļvietne $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Liegtā domēna vārda izmaiņas sglabātas" + }, "excludedDomainsSavedSuccess": { "message": "Saglabātas vērā neņemto domēna vārdu izmaiņas" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Izveidot lietotājvārdu" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nav nepieciešamo atļauju, lai labotu šo vienumu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Atslēgšana ar biometriju nav pieejama, jo Bitwarden darbvirsmas lietotne ir aizvērta." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, "authenticating": { "message": "Autentificē" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index cd210c85ce1..4cbbfc46d6a 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index d156e6d6458..cbb0b1bdf1a 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 16469051a0c..0e45e970ad1 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2324,6 +2324,9 @@ "message": "Domener", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Ekskluderte domener" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generer brukernavn" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autentiserer" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index d167ba220c1..0112ded1983 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2324,6 +2324,9 @@ "message": "Domeinen", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Geblokkeerde domeinen" + }, "excludedDomains": { "message": "Uitgesloten domeinen" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden zal voor deze domeinen niet vragen om de wachtwoorden op te slaan voor alle ingelogde accounts. Je moet de pagina verversen om de wijzigingen op te slaan." }, + "blockedDomainsDesc": { + "message": "Autofill en andere gerelateerde functies worden niet aangeboden voor deze websites. Vernieuw de pagina om de wijzigingen toe te passen." + }, + "autofillBlockedNotice": { + "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk of verander dit in de instellingen." + }, + "autofillBlockedTooltip": { + "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk in de instellingen." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Wijzigingen in geblokkeerde domeinen opgeslagen" + }, "excludedDomainsSavedSuccess": { "message": "Uitgesloten domeinwijzigingen opgeslagen" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gebruikersnamen genereren" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Je hebt geen toestemming om dit item te bewerken" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat de Bitwarden-desktopapp is afgesloten." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, "authenticating": { "message": "Aan het inloggen" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index a429059ea7d..4b7d3a19fc4 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2324,6 +2324,9 @@ "message": "Domeny", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Wykluczone domeny" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Aplikacja Bitwarden nie będzie proponować zapisywania danych logowania dla tych domen dla wszystkich zalogowanych kont. Musisz odświeżyć stronę, aby zastosowywać zmiany." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Strona internetowa $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Zmiany w wykluczonych domenach zapisane" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Wygeneruj nazwę użytkownika" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nie masz uprawnień do edycji tego elementu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Uwierzytelnianie" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index cd0c9979103..e3409b1da52 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2324,6 +2324,9 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domínios Excluídos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "O Bitwarden não irá pedir para salvar os detalhes de credencial para estes domínios. Você deve atualizar a página para que as alterações entrem em vigor." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Mudanças de domínios excluídos salvas" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gerar Usuário" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Você não tem permissão para editar este arquivo" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autenticando" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index def50289ae6..6b3c190f0b5 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2324,6 +2324,9 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domínios bloqueados" + }, "excludedDomains": { "message": "Domínios excluídos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "O Bitwarden não pedirá para guardar os detalhes de início de sessão destes domínios para todas as contas com sessão iniciada. É necessário atualizar a página para que as alterações tenham efeito." }, + "blockedDomainsDesc": { + "message": "O preenchimento automático e outras funcionalidades relacionadas não serão disponibilizados para estes sites. É necessário atualizar a página para que as alterações tenham efeito." + }, + "autofillBlockedNotice": { + "message": "O preenchimento automático está bloqueado para este site. Reveja ou altere esta opção nas definições." + }, + "autofillBlockedTooltip": { + "message": "O preenchimento automático está bloqueado neste site. Reveja nas definições." + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Alterações do domínio bloqueado guardadas" + }, "excludedDomainsSavedSuccess": { "message": "Alterações do domínio excluído guardadas" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gerar nome de utilizador" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Não tem permissão para editar este item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueio biométrico está atualmente indisponível." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "O desbloqueio biométrico não está disponível porque a app para computador Bitwarden está fechada." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." + }, "authenticating": { "message": "A autenticar" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 58c0a313f69..114c01aff44 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domenii excluse" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generare nume de utilizator" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index acdee563ceb..53f31813ac5 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2324,6 +2324,9 @@ "message": "Домены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблокированные домены" + }, "excludedDomains": { "message": "Исключенные домены" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не будет предлагать сохранение логинов для этих доменов для всех авторизованных аккаунтов. Для вступления изменений в силу необходимо обновить страницу." }, + "blockedDomainsDesc": { + "message": "Автозаполнение и другие связанные с ним функции не будут предлагаться для этих сайтов. Чтобы изменения вступили в силу, необходимо обновить страницу." + }, + "autofillBlockedNotice": { + "message": "Автозаполнение для этого сайта заблокировано. Просмотрите или измените это в настройках." + }, + "autofillBlockedTooltip": { + "message": "Автозаполнение на этом сайте заблокировано. Просмотрите в настройках." + }, "websiteItemLabel": { "message": "Сайт $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Изменения в заблокированном домене сохранены" + }, "excludedDomainsSavedSuccess": { "message": "Изменения в исключенном домене сохранены" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Создать имя пользователя" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "У вас нет разрешения на редактирование этого элемента" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Биометрическая разблокировка недоступна, поскольку Bitwarden для компьютера закрыт." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, "authenticating": { "message": "Аутентификация" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index b25c2fd30d5..56cf378344f 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "බැහැර වසම්" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 5d11227f003..08bfcc79f6a 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2324,6 +2324,9 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, "excludedDomains": { "message": "Vylúčené domény" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude požadovať ukladanie prihlasovacích údajov pre tieto domény pre všetky prihlásené účty. Aby sa zmeny prejavili, musíte stránku obnoviť." }, + "blockedDomainsDesc": { + "message": "Automatické vypĺňanie a ďalšie súvisiace funkcie sa na týchto webových stránkach nebudú ponúkať. Aby sa zmeny prejavili, musíte stránku obnoviť." + }, + "autofillBlockedNotice": { + "message": "Automatické vypĺňanie je pre túto webovú stránku zablokované. Skontrolujte alebo zmeňte to v nastaveniach." + }, + "autofillBlockedTooltip": { + "message": "Automatické vypĺňanie je na tejto webovej stránke zablokované. Pozrite v nastaveniach." + }, "websiteItemLabel": { "message": "Webstránka $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Zmeny v blokovaných doménach boli uložené" + }, "excludedDomainsSavedSuccess": { "message": "Uložené zmeny vylúčenej domény" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Vygenerovať používateľské meno" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Na úpravu tejto položky nemáte oprávnenie" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože aplikácia Bitwarden pre desktop je zatvorená." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je momentálne z neznámych dôvodov nedostupné." + }, "authenticating": { "message": "Overuje sa" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 490f991d252..2d2ee455415 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Izključene domene" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Napaka" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Ustvari uporabniško ime" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index d515c2a0c6b..01d95a6ed1b 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2324,6 +2324,9 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Изузети домени" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden неће тражити да сачува податке за пријављивање за ове домене за све пријављене налоге. Морате освежити страницу да би промене ступиле на снагу." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Сајт $number$ (УРЛ)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Изузете промене домена су сачуване" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генериши име" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Немате дозволу да уређујете ову ставку" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Аутентификација" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 84de9bbfa05..a443a8e6b2e 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -2324,6 +2324,9 @@ "message": "Domäner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Exkluderade domäner" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Webbplats $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generera användarnamn" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index d9ff1c3f076..1b493de3d2c 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index e1236b3f86d..8095a9f6045 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2324,6 +2324,9 @@ "message": "Alan adları", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Engellenen alan adları" + }, "excludedDomains": { "message": "Hariç tutulan alan adları" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, oturum açmış tüm hesaplar için bu alan adlarının hesap bilgilerini kaydetmeyi sormayacaktır. Değişikliklerin etkili olması için sayfayı yenilemeniz gerekir." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web sitesi $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Engelli alan adı değişiklikleri kaydedildi" + }, "excludedDomainsSavedSuccess": { "message": "Alan adı istisnası değişiklikleri kaydedildi" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Kullanıcı adı oluştur" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Bu kaydı düzenleme yetkisine sahip değilsiniz" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Kimlik doğrulanıyor" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 8e20bc56ff5..d6b0b88ead2 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2324,6 +2324,9 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблоковані домени" + }, "excludedDomains": { "message": "Виключені домени" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не запитуватиме про збереження даних входу для цих доменів для всіх облікових записів, до яких виконано вхід. Потрібно оновити сторінку для застосування змін." }, + "blockedDomainsDesc": { + "message": "Автозаповнення та інші пов'язані функції не пропонуватимуться для цих вебсайтів. Вам слід оновити сторінку для застосування змін." + }, + "autofillBlockedNotice": { + "message": "Автозаповнення заблоковано для цього вебсайту. Перегляньте або змініть це в налаштуваннях." + }, + "autofillBlockedTooltip": { + "message": "Автозаповнення заблоковано на цьому вебсайті. Перевірте налаштування." + }, "websiteItemLabel": { "message": "Вебсайт $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Зміни заблокованих доменів збережено" + }, "excludedDomainsSavedSuccess": { "message": "Виняток для домену збережено" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генерувати ім'я користувача" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Вам не дозволено редагувати цей запис" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Аутентифікація" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 268b12a6254..4ccdaf808f3 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2324,6 +2324,9 @@ "message": "Các tên miền", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Tên miền đã loại trừ" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden sẽ không yêu cầu lưu thông tin đăng nhập cho các miền này. Bạn phải làm mới trang để các thay đổi có hiệu lực." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Trang Web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Các thay đổi tên miền loại trừ đã được lưu" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Lỗi" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Tạo tên người dùng" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 69c1194af47..f11f0860d19 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", + "message": "无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -855,13 +855,13 @@ "message": "登录到 Bitwarden" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -2324,6 +2324,9 @@ "message": "域名", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "已屏蔽的域名" + }, "excludedDomains": { "message": "排除域名" }, @@ -2331,7 +2334,16 @@ "message": "Bitwarden 将不会询问是否为这些域名保存登录信息。您必须刷新页面才能使更改生效。" }, "excludedDomainsDescAlt": { - "message": "Bitwarden 不会询问保存所有已登录的账户的这些域名的登录信息。必须刷新页面才能使更改生效。" + "message": "Bitwarden 将不会询问是否为所有已登录账户的这些域名保存登录信息。您必须刷新页面才能使更改生效。" + }, + "blockedDomainsDesc": { + "message": "将不会为这些网站提供自动填充和其他相关功能。您必须刷新页面才能使更改生效。" + }, + "autofillBlockedNotice": { + "message": "该网站的自动填充功能已被阻止。请在设置中查看或更改。" + }, + "autofillBlockedTooltip": { + "message": "该网站的自动填充功能已被阻止。请在设置中查看。" }, "websiteItemLabel": { "message": "网站 $number$ (URI)", @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "已屏蔽的域名更改已保存" + }, "excludedDomainsSavedSuccess": { "message": "排除域名更改已保存" }, @@ -2601,7 +2616,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2789,6 +2804,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "生成用户名" }, @@ -2810,7 +2839,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2820,7 +2849,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3413,11 +3442,11 @@ "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "导入中...", + "message": "正在导入...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "数据导入成功!", + "message": "数据成功导入!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { @@ -3926,7 +3955,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "将 Bitwarden 设置为您的默认密码管理器吗?", + "message": "将 Bitwarden 设置为默认密码管理器吗?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { @@ -3934,7 +3963,7 @@ "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "将 Bitwarden 设置为您的默认密码管理器", + "message": "将 Bitwarden 设置为默认密码管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { @@ -3991,7 +4020,7 @@ "message": "没有搜索到匹配的项目" }, "clearFiltersOrTryAnother": { - "message": "清除筛选器或尝试另一个搜索词" + "message": "清除筛选或尝试另一个搜索词" }, "copyInfoTitle": { "message": "复制信息 - $ITEMNAME$", @@ -4567,7 +4596,7 @@ "message": "账户操作" }, "showNumberOfAutofillSuggestions": { - "message": "在扩展图标上显示自动填充建议的登录的数量" + "message": "在扩展图标上显示自动填充建议的登录数量" }, "showQuickCopyActions": { "message": "在密码库上显示快速复制操作" @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "您没有编辑此项目的权限" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "生物识别解锁不可用,因为 Bitwarden 桌面 App 已关闭。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, "authenticating": { "message": "正在验证" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 4b215df6ba2..467deffd815 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -20,7 +20,7 @@ "message": "建立帳戶" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "第一次使用 Bitwarden?" }, "logInWithPasskey": { "message": "使用密碼金鑰登入" @@ -29,7 +29,7 @@ "message": "使用單一登入" }, "welcomeBack": { - "message": "Welcome back" + "message": "歡迎回來" }, "setAStrongPassword": { "message": "設定一個強密碼" @@ -84,7 +84,7 @@ "message": "加入組織" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "加入 $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -120,7 +120,7 @@ "message": "複製密碼" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "複製密碼短語" }, "copyNote": { "message": "複製備註" @@ -153,13 +153,13 @@ "message": "複製駕照號碼" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "複製私密金鑰" }, "copyPublicKey": { - "message": "Copy public key" + "message": "複製公開金鑰" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "複製指紋" }, "copyCustomField": { "message": "複製 $FIELD$", @@ -177,7 +177,7 @@ "message": "複製備註" }, "fill": { - "message": "Fill", + "message": "填入", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +193,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": { @@ -337,22 +337,22 @@ "message": "您可以使用 Bitwarden 驗證器儲存驗證器金鑰,並為兩步驟驗證流程產生 TOTP 代碼。前往 bitwarden.com 網站以了解更多資訊。" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Bitwarden 密鑰管理" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "使用 Bitwarden 密鑰管理來安全儲存、管理並分享開發人員密鑰。在 bitwarden.com 網站上了解更多。" }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "使用 Passwordless.dev 建立流暢且安全的登入體驗,無需使用傳統密碼。在 bitwarden 網站上了解更多。" }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "免費的 Bitwarden 家庭方案" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "您符合免費 Bitwarden 家庭方案的資格要求。在網頁應用程式中兌換您的優惠。" }, "version": { "message": "版本" @@ -379,16 +379,16 @@ "message": "資料夾名稱" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "在資料夾名稱後面使用「/」來建立樹狀結構。\n例如:社交網路/論壇" }, "noFoldersAdded": { - "message": "No folders added" + "message": "未新增資料夾" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "建立資料夾來管理您的密碼庫項目" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "您確定要永久刪除此資料夾嗎?" }, "deleteFolder": { "message": "刪除資料夾" @@ -431,7 +431,7 @@ "message": "自動產生安全、唯一的登入密碼。" }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden 網頁應用程式" }, "importItems": { "message": "匯入項目" @@ -443,7 +443,7 @@ "message": "產生密碼" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "產生密碼短語" }, "regeneratePassword": { "message": "重新產生密碼" @@ -475,7 +475,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "包含大寫字元", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -491,7 +491,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "包含數字", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -499,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "包含特殊字元", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -526,11 +526,11 @@ "message": "最少符號位數" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "避免易混淆的字元", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "企業原則之要求已在你的產生器選項中生效", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -564,19 +564,19 @@ "message": "我的最愛" }, "unfavorite": { - "message": "Unfavorite" + "message": "取消最愛" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "項目已加入到最愛" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "項目已從最愛中移除" }, "notes": { "message": "備註" }, "privateNote": { - "message": "Private note" + "message": "私人備註" }, "note": { "message": "備註" @@ -600,7 +600,7 @@ "message": "開啟網站" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "開啟網站 $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -651,7 +651,7 @@ "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "您的密碼庫已被鎖定" }, "yourAccountIsLocked": { "message": "您的帳戶已被鎖定。" @@ -742,7 +742,7 @@ "message": "主密碼" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "若您忘記主密碼,將會無法找回!" }, "masterPassHintLabel": { "message": "您已成功創建新帳戶!" @@ -782,7 +782,7 @@ "message": "您已成功創建新帳戶!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "你已經登入!" }, "youSuccessfullyLoggedIn": { "message": "登入成功" @@ -797,7 +797,7 @@ "message": "必須填入驗證碼。" }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "驗證已被取消或時間超過。請再試一次。" }, "invalidVerificationCode": { "message": "無效的驗證碼" @@ -834,7 +834,7 @@ "message": "Bitwarden 可以儲存並填入兩步驟驗證碼。選擇相機圖示來截取此網站的驗證器QR code,或手動複製金鑰並貼上到此欄位。" }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "了解更多驗證程式" }, "copyTOTP": { "message": "複製驗證器金鑰 (TOTP)" @@ -852,19 +852,19 @@ "message": "登入" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "登入 Bitwarden" }, "restartRegistration": { - "message": "Restart registration" + "message": "重新啟動註冊" }, "expiredLink": { - "message": "Expired link" + "message": "過期連結" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "請重新啟動註冊流程或是重試登入。" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "您可能已經有帳號" }, "logOutConfirmation": { "message": "您確定要登出嗎?" @@ -888,7 +888,7 @@ "message": "兩步驟登入需要您從其他裝置(例如安全鑰匙、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這使您的帳戶更加安全。兩步驟登入可以在 bitwarden.com 網頁版密碼庫啟用。現在要前往嗎?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "在 Bitwarden 網頁應用程式中設定兩步驟登入,讓您的帳號更加安全。" }, "twoStepLoginConfirmationTitle": { "message": "前往網頁 App 嗎?" @@ -934,7 +934,7 @@ "message": "新增 URI" }, "addDomain": { - "message": "Add domain", + "message": "新增網域", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -1005,7 +1005,7 @@ "message": "於分頁頁面顯示身分以便於自動填入。" }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "在密碼庫檢視中點擊項目來自動填入" }, "clearClipboard": { "message": "清除剪貼簿", @@ -1317,10 +1317,10 @@ "message": "輸入驗證器應用程式提供的 6 位數驗證碼。" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "驗證工作階段因時間過久已逾時。請重試登入。" }, "enterVerificationCodeEmail": { "message": "輸入已傳送至 $EMAIL$ 的 6 位數驗證碼。", @@ -1386,17 +1386,17 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1413,7 +1413,7 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "selfHostedEnvironment": { "message": "自我裝載環境" @@ -1422,13 +1422,13 @@ "message": "指定您內部部署的 Bitwarden 安裝之基礎 URL。" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 URL。例如:https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "適用於進階設定。您可以單獨指定各個服務的網域 URL。" }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" @@ -1440,7 +1440,7 @@ "message": "伺服器 URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1466,22 +1466,22 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "自動填入建議" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "在表單欄位上顯示自動填入選單" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "顯示身分建議" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "顯示信用卡建議" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "當選擇圖示時,顯示建議" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "適用於所有已登入的帳戶。" }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "關閉你的瀏覽器內建密碼管理器設定以避免衝突。" @@ -1502,7 +1502,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "頁面載入時自動填入" }, "enableAutoFillOnPageLoad": { "message": "頁面載入時自動填入" @@ -1514,7 +1514,7 @@ "message": "被入侵或不被信任的網站,可能會濫用頁面載入的自動填入功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "了解更多風險" }, "learnMoreAboutAutofill": { "message": "進一步瞭解「自動填入」功能" @@ -1544,13 +1544,13 @@ "message": "在側邊欄中開啟密碼庫" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "自動將上次使用的登入資料填入目前網站" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "自動將上次使用的信用卡填入目前網站" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "自動將上次使用的身分資料填入目前網站" }, "commandGeneratePasswordDesc": { "message": "產生一組新的隨機密碼並將它複製到剪貼簿中。" @@ -1768,7 +1768,7 @@ "message": "身分" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "newItemHeader": { "message": "新增 $TYPE$", @@ -1801,13 +1801,13 @@ "message": "密碼歷史記錄" }, "generatorHistory": { - "message": "Generator history" + "message": "產生器歷史記錄" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "清除產生器歷史記錄" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "若繼續,所有產生器曾經產生的記錄會被刪除。您確定要繼續?" }, "back": { "message": "返回" @@ -1846,7 +1846,7 @@ "message": "安全筆記" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH 金鑰" }, "clear": { "message": "清除", @@ -1929,10 +1929,10 @@ "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "您最近未產生任何密碼" }, "remove": { "message": "移除" @@ -2002,7 +2002,7 @@ "message": "設定您用來解鎖 Bitwarden 的 PIN 碼。您的 PIN 設定將在您完全登出本應用程式時被重設。" }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "您的 PIN 碼會取代主密碼用來解鎖 Bitwarden。您的 PIN 碼會重置,若您完全登出 Bitwarden。" }, "pinRequired": { "message": "必須填入 PIN 碼。" @@ -2017,7 +2017,7 @@ "message": "使用生物特徵辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "awaitDesktop": { "message": "等待來自桌面應用程式的確認" @@ -2029,7 +2029,7 @@ "message": "瀏覽器重啟後使用主密碼鎖定" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "瀏覽器重啟後使用主密碼鎖定" }, "selectOneCollection": { "message": "您必須至少選擇一個集合。" @@ -2041,26 +2041,26 @@ "message": "克隆" }, "passwordGenerator": { - "message": "Password generator" + "message": "密碼產生器" }, "usernameGenerator": { - "message": "Username generator" + "message": "使用者名稱產生器" }, "useThisPassword": { - "message": "Use this password" + "message": "使用此密碼" }, "useThisUsername": { - "message": "Use this username" + "message": "使用此使用者名稱" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "已產生安全的密碼!請不要忘記同時更新您網站上的密碼。" }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "使用產生器", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "來產生高強度且唯一的密碼", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultTimeoutAction": { @@ -2096,7 +2096,7 @@ "message": "項目已還原" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "已經有帳號了嗎?" }, "vaultTimeoutLogOutConfirmation": { "message": "選擇登出將會在密碼庫逾時後移除對密碼庫的所有存取權限,若要重新驗證則需連線網路。確定要使用此設定嗎?" @@ -2108,7 +2108,7 @@ "message": "自動填入並儲存" }, "fillAndSave": { - "message": "Fill and save" + "message": "填入並儲存" }, "autoFillSuccessAndSavedUri": { "message": "項目已自動填入並且已儲存統一資源標識符(URI)" @@ -2189,19 +2189,19 @@ "message": "您新的主密碼不符合原則要求。" }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "獲得來自 Bitwarden 的公告、建議及研究資訊電子郵件。" }, "unsubscribe": { - "message": "Unsubscribe" + "message": "取消訂閱" }, "atAnyTime": { - "message": "at any time." + "message": "在任何時間。" }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "若是繼續,則代表您同意" }, "and": { - "message": "and" + "message": "和" }, "acceptPolicies": { "message": "選中此選取框,即表示您同意下列條款:" @@ -2222,10 +2222,10 @@ "message": "確定" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "存取權杖更新失敗" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "未找到存取權杖或 API 密鑰。請重試登出再登入。" }, "desktopSyncVerificationTitle": { "message": "桌面同步驗證" @@ -2264,10 +2264,10 @@ "message": "帳戶不相符" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "生物辨識金鑰錯誤" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "生物辨識解鎖失敗。生物辨識金鑰解鎖密碼庫失敗。請嘗試重新設定生物辨識。" }, "biometricsNotEnabledTitle": { "message": "生物特徵辨識未設定" @@ -2282,16 +2282,16 @@ "message": "此裝置不支援瀏覽器生物特徵辨識。" }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "使用者已鎖定或登出" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "請在桌面應用程式解鎖此使用者之後再重試。" }, "biometricsNotAvailableTitle": { "message": "生物辨識解鎖不可用" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "生物辨識解鎖現在無法使用。請稍後重試。" }, "biometricsFailedTitle": { "message": "生物特徵辨識失敗" @@ -2324,6 +2324,9 @@ "message": "網域", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "已封鎖的網域" + }, "excludedDomains": { "message": "排除網域" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "對於所有已登入的帳戶,Bitwarden 不會詢問是否儲存這些網域的登入資訊。您必須重新整理頁面變更才會生效。" }, + "blockedDomainsDesc": { + "message": "自動填入及其它相關的功能無法在這些網站上使用。您必須重新整理頁面來使變更生效。" + }, + "autofillBlockedNotice": { + "message": "自動填入已在此網站被封鎖。請在設定中檢視或更改此限制。" + }, + "autofillBlockedTooltip": { + "message": "自動填入已在此網站被封鎖。請在設定中檢視。" + }, "websiteItemLabel": { "message": "網站 $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "已儲存封鎖的網域" + }, "excludedDomainsSavedSuccess": { "message": "例外網域更改已儲存" }, @@ -2789,6 +2804,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "來避免更多資料遺失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "產生使用者名稱" }, @@ -2887,7 +2916,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "網站:$WEBSITE$。透過 Bitwarden 產生。", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2897,7 +2926,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "無效的 $SERVICENAME$ API 權杖", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2907,7 +2936,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "無效的 $SERVICENAME$ API 權杖:$ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2921,7 +2950,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "無法獲得 $SERVICENAME$ 的轉送電子郵件帳號。", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2931,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "無效的 $SERVICENAME$ 網域。", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2941,7 +2970,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "無效的 $SERVICENAME$ URI。", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2951,7 +2980,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "發生未知的 $SERVICENAME$ 錯誤。", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2961,7 +2990,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "未知的轉送服務提供商:$SERVICENAME$。", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3068,25 +3097,25 @@ "message": "重新傳送通知" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "檢視所有登入選項" }, "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "檢視所有登入選項" }, "notificationSentDevice": { "message": "已傳送通知至您的裝置。" }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "已傳送通知至您的裝置" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "請確保您的帳號已解鎖,並且指紋短語與其他裝置一致。" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "一旦您的請求被通過,您會獲得通知。" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "需要另一個選項嗎?" }, "loginInitiated": { "message": "登入已啟動" @@ -3146,22 +3175,22 @@ "message": "自動填入設定" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "自動填入快速鍵" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "修改鍵盤快速鍵" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "管理鍵盤快速鍵" }, "autofillShortcut": { "message": "自動填入鍵盤快速鍵" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "自動填入快速鍵尚未設定。請在瀏覽器的設定中變更。" }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "自動填入快速鍵是 $COMMAND$。請在瀏覽器的設定中變更。", "placeholders": { "command": { "content": "$1", @@ -3182,16 +3211,16 @@ "message": "在新視窗開啟" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "記住此裝置來讓未來的登入體驗更簡易" }, "deviceApprovalRequired": { "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "需要核准裝置" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "選擇下面的一個核准選項" }, "rememberThisDevice": { "message": "記住這個裝置" @@ -3212,25 +3241,25 @@ "message": "需要組織 SSO 識別碼。" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "建立帳號於" }, "checkYourEmail": { - "message": "Check your email" + "message": "檢查您的電子郵件" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "跟隨電子郵件中的連結" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "並繼續建立您的帳號" }, "noEmail": { - "message": "No email?" + "message": "沒有電子郵件?" }, "goBack": { - "message": "Go back" + "message": "返回" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "來編輯您的電子郵件位址。" }, "eu": { "message": "歐盟", @@ -3267,17 +3296,17 @@ "message": "缺少使用者電子郵件地址" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "未找到使用中帳號的電子郵件。正在將您登出。" }, "deviceTrusted": { "message": "裝置已信任" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "沒有可用的 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "使用 Send 可以與任何人安全地共用加密資訊。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3354,10 +3383,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "您需注意上方的 1 個欄位。" }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "您需注意上方的 $COUNT$ 個欄位。", "placeholders": { "count": { "content": "$1", @@ -3444,7 +3473,7 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -3466,7 +3495,7 @@ "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "解鎖您的帳號來查看建議的自動填入", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -3474,15 +3503,15 @@ "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "解鎖您的帳號,並開啟在新視窗", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "基於時間的一次性驗證碼", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "現在的 TOTP 到期剩餘時間", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { @@ -3510,23 +3539,23 @@ "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "新增新的密碼庫登入項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "新信用卡", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "新增新的密碼庫信用卡項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "新身份識別", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "新增新的密碼庫身分項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3616,19 +3645,19 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "彈出擴充套件視窗來完成登入。" }, "popoutExtension": { - "message": "Popout extension" + "message": "彈出擴充套件視窗" }, "launchDuo": { "message": "開啟Duo" @@ -3646,7 +3675,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3705,16 +3734,16 @@ "message": "確認檔案密碼" }, "exportSuccess": { - "message": "Vault data exported" + "message": "密碼庫資料已匯出" }, "typePasskey": { "message": "密碼金鑰" }, "accessing": { - "message": "Accessing" + "message": "正在存取" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "已登入!" }, "passkeyNotCopied": { "message": "密碼金鑰不會被複製" @@ -3738,7 +3767,7 @@ "message": "您沒有符合該網站的登入資訊。" }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "未找到此網站的登入資訊" }, "searchSavePasskeyNewLogin": { "message": "搜尋或將密碼金鑰儲存為新的登入資訊" @@ -3877,19 +3906,19 @@ "message": "伺服器" }, "hostedAt": { - "message": "hosted at" + "message": "架設在" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "使用您的裝置或密碼金鑰" }, "justOnce": { "message": "僅此一次" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "永遠針對此網站" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ 已新增到排除的網域。", "placeholders": { "domain": { "content": "$1", @@ -3898,51 +3927,51 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "常見格式", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "繼續前往瀏覽器設定?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "繼續前往說明中心?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "在您瀏覽器的偏好設定中更改自動填入及密碼管理。", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "您可以在您瀏覽器的偏好設定中檢視及設定擴充套件的快速鍵。", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "在您瀏覽器的偏好設定中更改自動填入及密碼管理。", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "您可以在您瀏覽器的偏好設定中檢視及設定擴充套件的快速鍵。", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "使用 Bitwarden 作為預設的密碼管理器?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "忽略此設定可能會導致 Bitwarden 自動填入選單與您的瀏覽器產生衝突。", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "使用 Bitwarden 作為預設的密碼管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "無法設定 Bitwarden 作為預設的密碼管理器", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "您必須同意您瀏覽器的隱私權限設定來設定 Bitwarden 為預設的密碼管理器。", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { @@ -3950,19 +3979,19 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "憑證資訊成功儲存!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "密碼已儲存!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "憑證資訊成功更新!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "密碼已更新!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -3970,7 +3999,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "成功" }, "removePasskey": { "message": "移除密碼金鑰" @@ -3979,19 +4008,19 @@ "message": "密碼金鑰已移除" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "自動填入建議" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "對此網站儲存登入項目為自動填入" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "您的密碼庫是空的" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "沒有找到相符的項目。" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "清除過濾器或更換另一個搜尋條件" }, "copyInfoTitle": { "message": "複製資訊 - $ITEMNAME$", @@ -4004,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "複製備註 - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4034,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "檢視項目 - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4044,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "自動填入 - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4054,22 +4083,22 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "沒有資料可以複製" }, "assignToCollections": { "message": "指派至集合" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copyPhone": { - "message": "Copy phone" + "message": "複製電話" }, "copyAddress": { - "message": "Copy address" + "message": "複製地址" }, "adminConsole": { - "message": "Admin Console" + "message": "管理控制台" }, "accountSecurity": { "message": "帳戶安全性" @@ -4081,13 +4110,13 @@ "message": "外觀" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "指定目標集合時發生錯誤。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "指定目標資料夾時發生錯誤。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "檢視 $NAME$ 中的項目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4097,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4107,7 +4136,7 @@ } }, "new": { - "message": "New" + "message": "新增" }, "removeItem": { "message": "移除 $NAME$", @@ -4120,16 +4149,16 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "不在任何資料夾中的項目" }, "itemDetails": { "message": "項目詳細資訊" }, "itemName": { - "message": "Item name" + "message": "項目名稱" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "若您只有檢視權限,無法移除集合 $COLLECTIONS$。", "placeholders": { "collections": { "content": "$1", @@ -4138,47 +4167,47 @@ } }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "組織已被停用" }, "owner": { - "message": "Owner" + "message": "擁有者" }, "selfOwnershipLabel": { - "message": "You", + "message": "您", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "無法存取已停用組織中的項目。請聯絡您組織的擁有者以獲取協助。" }, "additionalInformation": { - "message": "Additional information" + "message": "更多資訊" }, "itemHistory": { - "message": "Item history" + "message": "項目歷史記錄" }, "lastEdited": { - "message": "Last edited" + "message": "最後編輯" }, "ownerYou": { - "message": "Owner: You" + "message": "擁有者: 您" }, "linked": { - "message": "Linked" + "message": "連結" }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "upload": { - "message": "Upload" + "message": "上傳" }, "addAttachment": { - "message": "Add attachment" + "message": "新增附件" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "最大檔案大小為 500MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "刪除附檔 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4187,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "下載 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4196,25 +4225,25 @@ } }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "您確定要永久刪除此附檔嗎?" }, "premium": { "message": "進階版" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "免費組織無法使用附檔" }, "filters": { - "message": "Filters" + "message": "篩選器" }, "filterVault": { - "message": "Filter vault" + "message": "過濾密碼庫" }, "filterApplied": { - "message": "One filter applied" + "message": "套用了一個過濾條件" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "套用了 $COUNT$ 個過濾條件", "placeholders": { "count": { "content": "$1", @@ -4226,13 +4255,13 @@ "message": "個人資訊" }, "identification": { - "message": "Identification" + "message": "身份" }, "contactInfo": { - "message": "Contact info" + "message": "聯繫資訊" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "下載 - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4241,23 +4270,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "以此號碼結尾的信用卡", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "登入資訊" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "驗證器金鑰" }, "autofillOptions": { "message": "自動填入選項" }, "websiteUri": { - "message": "Website (URI)" + "message": "網站 (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "網站 (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4267,16 +4296,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "網站已新增" }, "addWebsite": { - "message": "Add website" + "message": "新增網站" }, "deleteWebsite": { - "message": "Delete website" + "message": "刪除網站" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "預設 ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4286,7 +4315,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "顯示偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4295,7 +4324,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "隱藏偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4304,13 +4333,13 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "在頁面載入時自動填寫?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "過期的信用卡" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "如果您已續期,請更新信用卡資訊" }, "cardDetails": { "message": "信用卡詳細資料" @@ -4334,17 +4363,17 @@ "message": "新增帳戶" }, "loading": { - "message": "Loading" + "message": "正在載入" }, "data": { - "message": "Data" + "message": "資料" }, "passkeys": { "message": "密碼金鑰", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "密碼", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { @@ -4352,16 +4381,16 @@ "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "指定" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "只有可以檢視集合的組織成員才能看到其中的項目。" }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "只有可以檢視集合的組織成員才能看到其中的項目。" }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "您已經選擇 $TOTAL_COUNT$ 個項目。由於您沒有編輯權限,無法更新其中的 $READONLY_COUNT$ 個項目。", "placeholders": { "total_count": { "content": "$1", @@ -4373,37 +4402,37 @@ } }, "addField": { - "message": "Add field" + "message": "新增欄位" }, "add": { - "message": "Add" + "message": "新增" }, "fieldType": { - "message": "Field type" + "message": "欄位類型" }, "fieldLabel": { - "message": "Field label" + "message": "欄位標籤" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "像是安全問答的資訊可以使用文字欄位來儲存" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "像是密碼的機密資訊可以使用隱藏欄位來儲存" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "若您想自動填入欄位中的核取方塊,例如儲存電子郵件,可以使用核取方塊。" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "使用連結欄位若您在特定網站上遇到自動填入問題。" }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "填入欄位的 html id、名稱、標籤或預留字元" }, "editField": { - "message": "Edit field" + "message": "編輯欄位" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "編輯 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4412,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "刪除 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ 已新增", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4459,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "重新排序 $LABEL$。使用方向鍵來往上或下移動。", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4468,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "往上移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4459,10 +4488,10 @@ "message": "選擇要指派的集合" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 個項目會被永久移到選擇的組織。您將不再擁有此項目。" }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ 個項目會被永久移到選擇的組織。您將不再擁有這些項目。", "placeholders": { "personal_items_count": { "content": "$1", @@ -4471,7 +4500,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 個項目會被永久移到 $ORG$。您將不再擁有此項目。", "placeholders": { "org": { "content": "$1", @@ -4480,7 +4509,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ 個項目會被永久移到 $ORG$。您將不再擁有這些項目。", "placeholders": { "personal_items_count": { "content": "$1", @@ -4496,10 +4525,10 @@ "message": "指派集合成功" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "您沒有選擇任何項目。" }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "將已選取項目移動至 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4508,7 +4537,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4517,7 +4546,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4526,7 +4555,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "往下移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4543,52 +4572,52 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "項目位置" }, "fileSend": { - "message": "File Send" + "message": "檔案 Send" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSend": { - "message": "Text Send" + "message": "文字 Send" }, "textSends": { - "message": "Text Sends" + "message": "文字 Sends" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden 有了新外觀!" }, "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "更容易使用的自動填入及密碼庫搜尋體驗。試試看吧!" }, "accountActions": { - "message": "Account actions" + "message": "帳號動作" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "在擴充套件圖示上顯示自動填入建議的數量" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "在密碼庫中顯示快速複製" }, "systemDefault": { - "message": "System default" + "message": "系統預設值" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "企業政策已套用至您的選項中" }, "sshPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshPublicKey": { - "message": "Public key" + "message": "公共金鑰" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "指紋" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "金鑰類型" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4603,223 +4632,250 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "重試" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "自訂逾時時間最小為 1 分鐘。" }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "以及更多內容" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "檔案已儲存到裝置。在您的裝置上管理下載檔案。" }, "showCharacterCount": { - "message": "Show character count" + "message": "顯示字元數" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "隱藏字元數" }, "itemsInTrash": { - "message": "Items in trash" + "message": "在垃圾桶中的項目" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "垃圾桶中沒有項目" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "您刪除的項目會在此顯示,並會在 30 天之後永久刪除" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "垃圾桶中超過 30 天的項目將會被自動刪除。" }, "restore": { - "message": "Restore" + "message": "還原" }, "deleteForever": { - "message": "Delete forever" + "message": "永遠刪除" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "你沒有權限編輯這個項目" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "由於 Bitwarden 桌面應用程式已關閉,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "由於未 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "基於不明原因,生物辨識解鎖無法使用。" }, "authenticating": { - "message": "Authenticating" + "message": "驗證中" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "自動填入產生的密碼", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "密碼已重新產生", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "在 Bitwarden 中儲存登入資訊?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "空格", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "波浪", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "重音符", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "驚歎號", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "在符號", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "井字號", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "錢字號", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "百分比", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "插入號", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "和符號", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "星號", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "左括號", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "右括號", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "底線", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "連字號", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "加號", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "等號", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "左大括號", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "右大括號", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "左中括號", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "右中括號", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "垂直符號", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "反斜線", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "冒號", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "分號", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "雙引號", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "單引號", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "小於", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "大於", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "逗號", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "句號", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "問號", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "斜線", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "小寫" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "大寫" }, "generatedPassword": { - "message": "Generated password" + "message": "已產生的密碼" }, "compactMode": { - "message": "Compact mode" + "message": "緊湊模式" }, "beta": { - "message": "Beta" + "message": "測試版" }, "importantNotice": { - "message": "Important notice" + "message": "重要通知" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "啟動兩階段登入" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" }, "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": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", "placeholders": { "email": { "content": "$1", @@ -4828,24 +4884,24 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "不,我不行" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "是,我可以存取我的電子郵件位址" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" }, "extensionWidth": { - "message": "Extension width" + "message": "擴充套件寬度" }, "wide": { - "message": "Wide" + "message": "寬度" }, "extraWide": { - "message": "Extra wide" + "message": "更寬" } } diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index ea98321a499..3348cf1910c 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -121,7 +121,7 @@ Bitwarden 密码管理器 - 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 + 无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 被 PCMag、WIRED、The Verge、CNET、G2 等评为最佳的密码管理器! @@ -167,7 +167,7 @@ Bitwarden 保护的不仅仅是密码 Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息! - 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 + 无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 在多个设备间同步和访问您的密码库 From f6a0496656d5a7ea04a0ce47b6468147cddc90e7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:00:40 -0500 Subject: [PATCH 133/270] [deps] AC: Update sass to v1.83.1 (#12302) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7994c8b0c2b..4867ac134cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -166,7 +166,7 @@ "process": "0.11.10", "remark-gfm": "4.0.0", "rimraf": "6.0.1", - "sass": "1.81.0", + "sass": "1.83.1", "sass-loader": "16.0.4", "storybook": "8.4.7", "style-loader": "4.0.0", @@ -28777,9 +28777,9 @@ } }, "node_modules/sass": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.81.0.tgz", - "integrity": "sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==", + "version": "1.83.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.1.tgz", + "integrity": "sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==", "dev": true, "license": "MIT", "dependencies": { @@ -28839,9 +28839,9 @@ } }, "node_modules/sass/node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 2508c543d7e..97e2b96f6a9 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "process": "0.11.10", "remark-gfm": "4.0.0", "rimraf": "6.0.1", - "sass": "1.81.0", + "sass": "1.83.1", "sass-loader": "16.0.4", "storybook": "8.4.7", "style-loader": "4.0.0", From 9e33e6960b4cae75e2e6e7ecd3bee8295e8ca863 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Fri, 10 Jan 2025 12:11:21 -0500 Subject: [PATCH 134/270] fix bad imports (#12803) --- apps/browser/src/autofill/content/notification-bar.ts | 5 ++--- apps/browser/src/autofill/types/index.ts | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts index 24b63d6b9eb..d3e9c29ab58 100644 --- a/apps/browser/src/autofill/content/notification-bar.ts +++ b/apps/browser/src/autofill/content/notification-bar.ts @@ -1,8 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ServerConfig } from "../../../../../libs/common/src/platform/abstractions/config/server-config"; +import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; + import { AddLoginMessageData, ChangePasswordMessageData, diff --git a/apps/browser/src/autofill/types/index.ts b/apps/browser/src/autofill/types/index.ts index 606b36e1fb4..58ac95e7edf 100644 --- a/apps/browser/src/autofill/types/index.ts +++ b/apps/browser/src/autofill/types/index.ts @@ -1,7 +1,5 @@ +import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum"; import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; import { CipherType } from "@bitwarden/common/vault/enums"; From 5fcd37833ce3a009f6295b6ce0b9ecefbc4f0358 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:51:20 -0500 Subject: [PATCH 135/270] [deps] Autofill: Update tldts to v6.1.71 (#12702) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 18 +++++++++--------- package.json | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 40f7933ef85..28432bd5558 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -80,7 +80,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.69", + "tldts": "6.1.71", "zxcvbn": "4.4.2" } } diff --git a/package-lock.json b/package-lock.json index 4867ac134cf..066a9781a91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,7 +70,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.69", + "tldts": "6.1.71", "utf-8-validate": "6.0.5", "zone.js": "0.14.10", "zxcvbn": "4.4.2" @@ -223,7 +223,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.69", + "tldts": "6.1.71", "zxcvbn": "4.4.2" }, "bin": { @@ -30719,21 +30719,21 @@ } }, "node_modules/tldts": { - "version": "6.1.69", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.69.tgz", - "integrity": "sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==", + "version": "6.1.71", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.71.tgz", + "integrity": "sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.69" + "tldts-core": "^6.1.71" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.69", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.69.tgz", - "integrity": "sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==", + "version": "6.1.71", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.71.tgz", + "integrity": "sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==", "license": "MIT" }, "node_modules/tmp": { diff --git a/package.json b/package.json index 97e2b96f6a9..12d89fa8bc3 100644 --- a/package.json +++ b/package.json @@ -201,7 +201,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.69", + "tldts": "6.1.71", "utf-8-validate": "6.0.5", "zone.js": "0.14.10", "zxcvbn": "4.4.2" From d0973529788ff66c88e9c7526fa0898c544a5ed0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:55:59 -0500 Subject: [PATCH 136/270] [deps] Platform: Update @types/node to v22.10.5 (#12703) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 4bf939c8b41..f7e70babc48 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -18,7 +18,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -106,9 +106,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 6ab267be4a8..39e43f3a971 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -23,7 +23,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/package-lock.json b/package-lock.json index 066a9781a91..0be1e2602ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,7 +113,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -9772,9 +9772,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 12d89fa8bc3..f03dc6c9d0c 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", From 0badd04033b7380c5b700c0ad09ccae3aeec6452 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:56:53 -0500 Subject: [PATCH 137/270] [deps] Autofill: Update concurrently to v9.1.2 (#12701) 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 0be1e2602ed..591478d3516 100644 --- a/package-lock.json +++ b/package-lock.json @@ -130,7 +130,7 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.20.2", - "concurrently": "9.1.0", + "concurrently": "9.1.2", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", @@ -13825,9 +13825,9 @@ } }, "node_modules/concurrently": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz", - "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", + "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index f03dc6c9d0c..3dcb2c142ed 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.20.2", - "concurrently": "9.1.0", + "concurrently": "9.1.2", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", From 2a30c27d18d14aa5f151e46ad94d3a28abef2844 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:58:44 -0500 Subject: [PATCH 138/270] [deps]: Update uuid to v11.0.5 (#12705) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index f7e70babc48..f727c903a7f 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -14,7 +14,7 @@ "module-alias": "2.2.3", "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { @@ -421,9 +421,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 39e43f3a971..b7da729d7d1 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -19,7 +19,7 @@ "module-alias": "2.2.3", "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { From b8f57f34642764cc91bd259fcbf48292b1e8ff81 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:00:57 -0800 Subject: [PATCH 139/270] [PM-12759] - Admin Console - Link to vault cipher is not opening cipher modal (#12738) * fix initial load emission race * prevent double dialog render * put logic back in place --- .../app/vault/org-vault/vault.component.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 645d81cec18..14550968ba5 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -32,7 +32,6 @@ import { switchMap, takeUntil, tap, - withLatestFrom, } from "rxjs/operators"; import { @@ -194,6 +193,7 @@ export class VaultComponent implements OnInit, OnDestroy { protected currentSearchText$: Observable; protected freeTrial$: Observable; protected resellerWarning$: Observable; + protected prevCipherId: string | null = null; /** * A list of collections that the user can assign items to and edit those items within. * @protected @@ -538,25 +538,26 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( - switchMap(() => this.route.queryParams), - // Only process the queryParams if the dialog is not open (only when extension refresh is enabled) + switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])), filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled), - withLatestFrom(allCipherMap$, allCollections$, organization$), switchMap(async ([qParams, allCiphersMap]) => { const cipherId = getCipherIdFromParams(qParams); + if (!cipherId) { + this.prevCipherId = null; return; } - const cipher = allCiphersMap[cipherId]; + if (cipherId === this.prevCipherId) { + return; + } + + this.prevCipherId = cipherId; + + const cipher = allCiphersMap[cipherId]; if (cipher) { let action = qParams.action; - // Default to "view" if extension refresh is enabled - if (action == null && this.extensionRefreshEnabled) { - action = "view"; - } - if (action == "showFailedToDecrypt") { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [cipherId as CipherId], @@ -569,6 +570,11 @@ export class VaultComponent implements OnInit, OnDestroy { return; } + // Default to "view" if extension refresh is enabled + if (action == null && this.extensionRefreshEnabled) { + action = "view"; + } + if (action === "view") { await this.viewCipherById(cipher); } else { From 3c994930acda68c656cc11de62b66840d3f31708 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:41:10 -0600 Subject: [PATCH 140/270] fix: replace bad import statements, refs PM-16928 (#12805) --- .../src/admin-console/device-approval/approve.command.ts | 4 +--- .../src/admin-console/device-approval/deny-all.command.ts | 4 +--- .../bit-cli/src/admin-console/device-approval/deny.command.ts | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts index f8ce50a8490..1c51f9397c5 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -1,12 +1,10 @@ import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class ApproveCommand { diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts index c3cbd00ad38..767acea99f7 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts @@ -2,14 +2,12 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { MessageResponse } from "@bitwarden/cli/models/response/message.response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyAllCommand { diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts index 1036602b0f8..87e633b2bee 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -1,12 +1,10 @@ import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyCommand { From e1434d8dd5d192bf732a332d7f0dfb8ffd3ad80c Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:21:12 -0800 Subject: [PATCH 141/270] [PM-16858] - adjust generator dialog action button to match browser extension UI (#12788) * adjust generator dialog buttons to match browser extension UI * put dialog label into generator config * fix types. remove i18n key * use event emitted pattern for getting algorithm config * favor arrow function * move function call * append key to i18n prop * fix test --- apps/desktop/src/locales/en/messages.json | 6 ++++++ .../app/vault/credential-generator-dialog.component.html | 8 +++----- .../app/vault/credential-generator-dialog.component.ts | 6 ++++++ .../components/src/password-generator.component.ts | 5 +++++ .../components/src/username-generator.component.ts | 5 +++++ libs/tools/generator/core/src/data/generators.ts | 6 ++++++ .../src/services/credential-generator.service.spec.ts | 2 ++ .../core/src/services/credential-generator.service.ts | 1 + .../core/src/types/credential-generator-configuration.ts | 6 ++++++ .../cipher-generator/cipher-form-generator.component.html | 2 ++ .../cipher-generator/cipher-form-generator.component.ts | 5 ++++- 11 files changed, 46 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 35d5cf8c03c..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html index a294ce1f63f..d16f8e4f1cb 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html @@ -4,6 +4,7 @@ -
    diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts index abf74371912..ae6f031005e 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -14,6 +14,7 @@ import { CredentialGeneratorHistoryDialogComponent, GeneratorModule, } from "@bitwarden/generator-components"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; type CredentialGeneratorParams = { @@ -38,12 +39,17 @@ type CredentialGeneratorParams = { }) export class CredentialGeneratorDialogComponent { credentialValue?: string; + buttonLabel?: string; constructor( @Inject(DIALOG_DATA) protected data: CredentialGeneratorParams, private dialogService: DialogService, ) {} + algorithm = (selected: AlgorithmInfo) => { + this.buttonLabel = selected.useGeneratedValue; + }; + applyCredentials = () => { this.data.onCredentialGenerated(this.credentialValue); }; diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index 67a1bf21839..85363412ffa 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -93,6 +93,10 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy { @Output() readonly onGenerated = new EventEmitter(); + /** emits algorithm info when the selected algorithm changes */ + @Output() + readonly onAlgorithm = new EventEmitter(); + async ngOnInit() { if (this.userId) { this.userId$.next(this.userId); @@ -185,6 +189,7 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy { // template bindings refresh immediately this.zone.run(() => { this.algorithm$.next(algorithm); + this.onAlgorithm.next(algorithm); }); }); diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index d0230dcba40..63c1adc602b 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -78,6 +78,10 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy { @Output() readonly onGenerated = new EventEmitter(); + /** emits algorithm info when the selected algorithm changes */ + @Output() + readonly onAlgorithm = new EventEmitter(); + /** Removes bottom margin from internal elements */ @Input({ transform: coerceBooleanProperty }) disableMargin = false; @@ -247,6 +251,7 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy { // template bindings refresh immediately this.zone.run(() => { this.algorithm$.next(algorithm); + this.onAlgorithm.next(algorithm); }); }); diff --git a/libs/tools/generator/core/src/data/generators.ts b/libs/tools/generator/core/src/data/generators.ts index afd7da6499d..92b20b02b75 100644 --- a/libs/tools/generator/core/src/data/generators.ts +++ b/libs/tools/generator/core/src/data/generators.ts @@ -58,6 +58,7 @@ const PASSPHRASE: CredentialGeneratorConfiguration< generateKey: "generatePassphrase", generatedValueKey: "passphrase", copyKey: "copyPassphrase", + useGeneratedValueKey: "useThisPassphrase", onlyOnRequest: false, request: [], engine: { @@ -119,6 +120,7 @@ const PASSWORD: CredentialGeneratorConfiguration< generateKey: "generatePassword", generatedValueKey: "password", copyKey: "copyPassword", + useGeneratedValueKey: "useThisPassword", onlyOnRequest: false, request: [], engine: { @@ -195,6 +197,7 @@ const USERNAME: CredentialGeneratorConfiguration = { @@ -83,6 +84,7 @@ const SomeConfiguration: CredentialGeneratorConfiguration diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts index 1a2bac2ce50..fdde5e15d91 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts @@ -5,7 +5,7 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { GeneratorModule } from "@bitwarden/generator-components"; -import { GeneratedCredential } from "@bitwarden/generator-core"; +import { AlgorithmInfo, GeneratedCredential } from "@bitwarden/generator-core"; /** * Renders a password or username generator UI and emits the most recently generated value. @@ -18,6 +18,9 @@ import { GeneratedCredential } from "@bitwarden/generator-core"; imports: [CommonModule, GeneratorModule], }) export class CipherFormGeneratorComponent { + @Input() + algorithm: (selected: AlgorithmInfo) => void; + /** * The type of generator form to show. */ From 4c8565f7f382f6e05fcf936fc04396db891e0711 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Sun, 12 Jan 2025 16:37:15 -0500 Subject: [PATCH 142/270] Modify Edge and Opera artifacts to build Mv3 - Part 1 (#12674) * Modify Edge and Chrome artifacts to build Mv3 version to mimic Chrome * Added back the Mv3 scripts so that workflows run on the PR will pass --- .github/workflows/build-browser.yml | 12 ++---------- apps/browser/package.json | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index aa62d602ad8..64cbaa0c7f1 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -196,11 +196,7 @@ jobs: - name: "edge" npm_command: "dist:edge" archive_name: "dist-edge.zip" - artifact_name: "dist-edge" - - name: "edge-mv3" - npm_command: "dist:edge:mv3" - archive_name: "dist-edge.zip" - artifact_name: "DO-NOT-USE-FOR-PROD-dist-edge-MV3" + artifact_name: "dist-edge-MV3" - name: "firefox" npm_command: "dist:firefox" archive_name: "dist-firefox.zip" @@ -212,11 +208,7 @@ jobs: - name: "opera" npm_command: "dist:opera" archive_name: "dist-opera.zip" - artifact_name: "dist-opera" - - name: "opera-mv3" - npm_command: "dist:opera:mv3" - archive_name: "dist-opera.zip" - artifact_name: "DO-NOT-USE-FOR-PROD-dist-opera-MV3" + artifact_name: "dist-opera-MV3" steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/apps/browser/package.json b/apps/browser/package.json index 3adeb292b6d..cf9309728ae 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -4,9 +4,9 @@ "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 webpack", - "build:edge": "cross-env BROWSER=edge webpack", + "build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 webpack", "build:firefox": "cross-env BROWSER=firefox webpack", - "build:opera": "cross-env BROWSER=opera webpack", + "build:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 webpack", "build:safari": "cross-env BROWSER=safari webpack", "build:watch": "npm run build:watch:chrome", "build:watch:chrome": "npm run build:chrome -- --watch", From 5dc523bacb7b1da5ea2773794bad91e2627ab701 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 13 Jan 2025 09:29:42 +0100 Subject: [PATCH 143/270] [PM-16895] Fix biometric prompt showing up in browser while disabled (#12781) --- .../extension-lock-component.service.spec.ts | 14 +++++++++++++- .../extension-lock-component.service.ts | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts index 4b0323d5ebe..92830c35aca 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts @@ -9,7 +9,12 @@ import { import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { + KeyService, + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; import { UnlockOptions } from "@bitwarden/key-management/angular"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; @@ -26,6 +31,7 @@ describe("ExtensionLockComponentService", () => { let vaultTimeoutSettingsService: MockProxy; let keyService: MockProxy; let routerService: MockProxy; + let biometricStateService: MockProxy; beforeEach(() => { userDecryptionOptionsService = mock(); @@ -35,6 +41,7 @@ describe("ExtensionLockComponentService", () => { vaultTimeoutSettingsService = mock(); keyService = mock(); routerService = mock(); + biometricStateService = mock(); TestBed.configureTestingModule({ providers: [ @@ -67,6 +74,10 @@ describe("ExtensionLockComponentService", () => { provide: BrowserRouterService, useValue: routerService, }, + { + provide: BiometricStateService, + useValue: biometricStateService, + }, ], }); @@ -306,6 +317,7 @@ describe("ExtensionLockComponentService", () => { platformUtilsService.supportsSecureStorage.mockReturnValue( mockInputs.platformSupportsSecureStorage, ); + biometricStateService.biometricUnlockEnabled$ = of(true); // PIN pinService.isPinDecryptionAvailable.mockResolvedValue(mockInputs.pinDecryptionAvailable); diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts index f21beb91cff..77c0fcaf50a 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -1,14 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { inject } from "@angular/core"; -import { combineLatest, defer, map, Observable } from "rxjs"; +import { combineLatest, defer, firstValueFrom, map, Observable } from "rxjs"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; -import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; @@ -19,6 +23,7 @@ export class ExtensionLockComponentService implements LockComponentService { private readonly biometricsService = inject(BiometricsService); private readonly pinService = inject(PinServiceAbstraction); private readonly routerService = inject(BrowserRouterService); + private readonly biometricStateService = inject(BiometricStateService); getPreviousUrl(): string | null { return this.routerService.getPreviousUrl(); @@ -45,7 +50,13 @@ export class ExtensionLockComponentService implements LockComponentService { getAvailableUnlockOptions$(userId: UserId): Observable { return combineLatest([ // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to - defer(async () => await this.biometricsService.getBiometricsStatusForUser(userId)), + defer(async () => { + if (!(await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$))) { + return BiometricsStatus.NotEnabledLocally; + } else { + return await this.biometricsService.getBiometricsStatusForUser(userId); + } + }), this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), defer(() => this.pinService.isPinDecryptionAvailable(userId)), ]).pipe( From 6872100f0705c7c8f5169ebdfd3f26660bc66c71 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 13 Jan 2025 09:30:30 +0100 Subject: [PATCH 144/270] Fix biometrics being disabled before permission is granted (#12792) --- .../src/auth/popup/settings/account-security.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 158eb797ac8..3b1727f89ac 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -206,6 +206,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { switchMap(async () => { const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id); const biometricSettingAvailable = + !(await BrowserApi.permissionsGranted(["nativeMessaging"])) || (status !== BiometricsStatus.DesktopDisconnected && status !== BiometricsStatus.NotEnabledInConnectedDesktopApp) || (await this.vaultTimeoutSettingsService.isBiometricLockSet()); From ae92ddf17707b09a3398950d5b72a496321a495c Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:34:24 +0000 Subject: [PATCH 145/270] Autosync the updated translations (#12825) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/de/messages.json | 22 +- apps/browser/src/_locales/it/messages.json | 8 +- apps/browser/src/_locales/nb/messages.json | 390 +++++++++--------- apps/browser/src/_locales/zh_CN/messages.json | 16 +- apps/browser/store/locales/zh_CN/copy.resx | 5 +- 5 files changed, 221 insertions(+), 220 deletions(-) diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index bc1158bfc21..fce84d1b431 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2325,7 +2325,7 @@ "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Blocked domains" + "message": "Gesperrte Domains" }, "excludedDomains": { "message": "Ausgeschlossene Domains" @@ -2337,7 +2337,7 @@ "message": "Bitwarden wird für alle angemeldeten Konten nicht danach fragen Zugangsdaten für diese Domains speichern. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, "blockedDomainsDesc": { - "message": "Autofill und andere zugehörige Funktionen werden für diese Websites nicht angeboten. Sie müssen die Seite aktualisieren, damit die Änderungen wirksam werden." + "message": "Automatisches Ausfüllen und andere zugehörige Funktionen werden für diese Webseiten nicht angeboten. Sie müssen die Seite aktualisieren, damit die Änderungen wirksam werden." }, "autofillBlockedNotice": { "message": "Das automatische Ausfüllen ist für diese Website gesperrt. Dieses Verhalten kann in den Einstellungen überprüft oder geändert werden." @@ -2364,7 +2364,7 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Blocked domain changes saved" + "message": "Änderungen gesperrter Domains gespeichert" }, "excludedDomainsSavedSuccess": { "message": "Änderungen der ausgeschlossenen Domain gespeichert" @@ -2815,7 +2815,7 @@ "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": "um zusätzlichen Datenverlust zu vermeiden.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { @@ -4671,22 +4671,22 @@ "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "Biometrisches Entsperren ist nicht verfügbar, da die Bitwarden Desktop-App geschlossen ist." }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", "placeholders": { "email": { "content": "$1", @@ -4695,7 +4695,7 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." }, "authenticating": { "message": "Authentifizierung" diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 0653623f078..04e2c4ee64f 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2337,13 +2337,13 @@ "message": "Bitwarden non chiederà di salvare le credenziali di accesso per questi domini per tutti gli account sul dispositivo. Ricarica la pagina affinché le modifiche abbiano effetto." }, "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": "Per questi siti, l'auto-completamento e funzionalità simili non saranno disponibili. Ricarica la pagina per applicare le modifiche." }, "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "message": "L'auto-completamento è bloccato per questo sito. Modifica questa scelta nelle impostazioni." }, "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "message": "L'auto-completamento è bloccato per questo sito. Verifica nelle impostazioni." }, "websiteItemLabel": { "message": "Sito $number$ (URI)", @@ -2364,7 +2364,7 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Blocked domain changes saved" + "message": "Modifiche ai domini bloccati salvate" }, "excludedDomainsSavedSuccess": { "message": "Modifiche del dominio escluso salvate" diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 0e45e970ad1..3a12c9ae4f4 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -20,19 +20,19 @@ "message": "Opprett en konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Er du ny til Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logg inn med passnøkkel" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Velkommen tilbake" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Velg et sterkt passord" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -138,31 +138,31 @@ "message": "Kopier sikkerhetskoden" }, "copyName": { - "message": "Copy name" + "message": "Kopiér navn" }, "copyCompany": { "message": "Copy company" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopiér fødselsnummer" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopiér passnummer" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopiér lisensnummer" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Kopiér privat nøkkel" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Kopiér offentlig nøkkel" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Kopiér fingeravtrykk" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopiér $FIELD$", "placeholders": { "field": { "content": "$1", @@ -171,13 +171,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopiér nettsted" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopiér notater" }, "fill": { - "message": "Fill", + "message": "Fyll", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +193,10 @@ "message": "Auto-utfyll identitet" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Fyll inn verifiseringskode" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Fyll inn verifiseringskode", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -239,13 +239,13 @@ "message": "Legg til en gjenstand" }, "accountEmail": { - "message": "Account email" + "message": "Kontoens E-postadresse" }, "requestHint": { - "message": "Request hint" + "message": "Be om et hint" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Be om passordhint" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" @@ -316,7 +316,7 @@ "message": "Logg ut" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Om Bitwarden" }, "about": { "message": "Om" @@ -325,7 +325,7 @@ "message": "More from Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Vil du fortsette til bitwarden.com?" }, "bitwardenForBusiness": { "message": "Bitwarden for Business" @@ -382,7 +382,7 @@ "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Ingen mapper er lagt til" }, "createFoldersToOrganize": { "message": "Create folders to organize your vault items" @@ -475,7 +475,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkluder store bokstaver", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkluder små bokstaver", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -499,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inkluder spesialtegn", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -526,7 +526,7 @@ "message": "Minste antall spesialtegn" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unngå forvekslingsbare tegn", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -564,19 +564,19 @@ "message": "Favoritt" }, "unfavorite": { - "message": "Unfavorite" + "message": "Fjern favorittstempel" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Gjenstand lagt til i favorittene" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Gjenstand fjernet fra favorittene" }, "notes": { "message": "Notater" }, "privateNote": { - "message": "Private note" + "message": "Privat notat" }, "note": { "message": "Notat" @@ -597,10 +597,10 @@ "message": "Åpne" }, "launchWebsite": { - "message": "Launch website" + "message": "Åpne nettstedet" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Åpne nettstedet $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -657,7 +657,7 @@ "message": "Your account is locked" }, "or": { - "message": "or" + "message": "eller" }, "unlock": { "message": "Lås opp" @@ -745,7 +745,7 @@ "message": "Your master password cannot be recovered if you forget it!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Få et hint om hovedpassordet" }, "errorOccurred": { "message": "En feil har oppstått" @@ -785,10 +785,10 @@ "message": "You have been logged in!" }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Du har vellykket logget inn" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Du kan lukke dette vinduet" }, "masterPassSent": { "message": "Vi har sendt deg en E-post med hintet til superpassordet." @@ -834,10 +834,10 @@ "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Lær mer om autentisering" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Kopier autentiseringsnøkkel (TOTP)" }, "loggedOut": { "message": "Logget av" @@ -852,19 +852,19 @@ "message": "Logg inn" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logg inn på Bitwarden" }, "restartRegistration": { "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Utløpt lenke" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du har kanskje allerede en konto" }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" @@ -1049,7 +1049,7 @@ "message": "Lås opp" }, "additionalOptions": { - "message": "Additional options" + "message": "Ekstra innstillinger" }, "enableContextMenuItem": { "message": "Vis alternativer for kontekstmeny" @@ -1113,7 +1113,7 @@ "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "exportTypeHeading": { - "message": "Export type" + "message": "Eksporttype" }, "accountRestricted": { "message": "Account restricted" @@ -1126,7 +1126,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "Advarsel", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1206,7 +1206,7 @@ "message": "Fil" }, "fileToShare": { - "message": "File to share" + "message": "Filen som skal deles" }, "selectFile": { "message": "Velg en fil." @@ -1242,7 +1242,7 @@ "message": "1 GB med kryptert fillagring for filvedlegg." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "Nødtilgang." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1413,7 +1413,7 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Skriv inn koden du har fått tilsendt på E-post." }, "selfHostedEnvironment": { "message": "Selvbetjent miljø" @@ -1466,7 +1466,7 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" @@ -1514,7 +1514,7 @@ "message": "Kompromitterte eller upålitelige nettsider kan utnytte auto-utfylling når du laster inn siden." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Lær mer om risikoer" }, "learnMoreAboutAutofill": { "message": "Lær mer om auto-utfylling" @@ -1768,10 +1768,10 @@ "message": "Identitet" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH-nøkkel" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Ny $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1780,7 +1780,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Rediger $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1789,7 +1789,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Vis $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1801,7 +1801,7 @@ "message": "Passordhistorikk" }, "generatorHistory": { - "message": "Generator history" + "message": "Generatorhistorikk" }, "clearGeneratorHistoryTitle": { "message": "Clear generator history" @@ -1846,7 +1846,7 @@ "message": "Sikre notiser" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH-nøkler" }, "clear": { "message": "Tøm", @@ -1929,7 +1929,7 @@ "message": "Tøm historikk" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Ingenting å vise" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -2017,7 +2017,7 @@ "message": "Lås opp med biometri" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Lås opp med hovedpassord" }, "awaitDesktop": { "message": "Venter på bekreftelse fra skrivebordsprogrammet" @@ -2029,7 +2029,7 @@ "message": "Lås med hovedpassordet når du starter nettleseren på nytt" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Krev hovedpassord ved omstart av nettleseren" }, "selectOneCollection": { "message": "Du må velge minst én samling." @@ -2041,22 +2041,22 @@ "message": "Klon" }, "passwordGenerator": { - "message": "Password generator" + "message": "Passordgenerator" }, "usernameGenerator": { - "message": "Username generator" + "message": "Brukernavngenerator" }, "useThisPassword": { "message": "Bruk dette passordet" }, "useThisUsername": { - "message": "Use this username" + "message": "Bruk dette brukernavnet" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Bruk denne generatoren", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { @@ -2096,7 +2096,7 @@ "message": "Gjenopprettet objekt" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Har du allerede en konto?" }, "vaultTimeoutLogOutConfirmation": { "message": "Hvis du logger ut, fjerner du all tilgang til hvelvet ditt og krever online godkjenning etter tidsavbrudd. Er du sikker på at du vil bruke denne innstillingen?" @@ -2108,7 +2108,7 @@ "message": "Autofyll og lagre" }, "fillAndSave": { - "message": "Fill and save" + "message": "Fyll og lagre" }, "autoFillSuccessAndSavedUri": { "message": "Autoutfylt objekt og lagret URI" @@ -2195,10 +2195,10 @@ "message": "Avslutt abonnement" }, "atAnyTime": { - "message": "at any time." + "message": "når som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Ved å fortsette, samtykker du til" }, "and": { "message": "og" @@ -2325,7 +2325,7 @@ "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Blocked domains" + "message": "Blokkerte domener" }, "excludedDomains": { "message": "Ekskluderte domener" @@ -2370,7 +2370,7 @@ "message": "Excluded domain changes saved" }, "limitSendViews": { - "message": "Limit views" + "message": "Begrens visninger" }, "limitSendViewsHint": { "message": "No one can view this Send after the limit is reached.", @@ -2398,7 +2398,7 @@ "message": "Tekst" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Teksten som skal deles" }, "sendTypeFile": { "message": "Fil" @@ -2634,7 +2634,7 @@ "message": "Velg mappe …" }, "noFoldersFound": { - "message": "No folders found", + "message": "Ingen mapper ble funnet", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { @@ -2646,7 +2646,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "av $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2805,7 +2805,7 @@ "message": "Feil" }, "decryptionError": { - "message": "Decryption error" + "message": "Dekrypteringsfeil" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden could not decrypt the vault item(s) listed below." @@ -2825,7 +2825,7 @@ "message": "Generate email" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2890,7 +2890,7 @@ "message": "Generer et e-postalias med en ekstern videresendingstjeneste." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomene", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2912,11 +2912,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Nettsted: $WEBSITE$. Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2960,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ugyldig $SERVICENAME$-domene.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3097,16 +3097,16 @@ "message": "Send varslingen på nytt" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "notificationSentDevice": { "message": "Et varsel er sendt til enheten din." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Et varsel ble sendt til enheten din" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" @@ -3115,7 +3115,7 @@ "message": "You will be notified once the request is approved" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Trenger du et annet alternativ?" }, "loginInitiated": { "message": "Login initiated" @@ -3217,7 +3217,7 @@ "message": "Device approval required. Select an approval option below:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Enhetsgodkjennelse kreves" }, "selectAnApprovalOptionBelow": { "message": "Select an approval option below" @@ -3241,7 +3241,7 @@ "message": "Organization SSO identifier is required." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Oppretter en konto på" }, "checkYourEmail": { "message": "Check your email" @@ -3259,7 +3259,7 @@ "message": "Gå tilbake" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "for å redigere E-postadressen din." }, "eu": { "message": "EU", @@ -3287,7 +3287,7 @@ "message": "Du vil bli varslet når det er godkjent." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Har du problemer med å logge inn?" }, "loginApproved": { "message": "Innlogging godkjent" @@ -3299,7 +3299,7 @@ "message": "Active user email not found. Logging you out." }, "deviceTrusted": { - "message": "Device trusted" + "message": "Enheten er betrodd" }, "sendsNoItemsTitle": { "message": "No active Sends", @@ -3450,7 +3450,7 @@ "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "Feil under importering. Sjekk loggkonsollen for detaljer.", "description": "Notification message for when an import has failed." }, "importNetworkError": { @@ -3487,7 +3487,7 @@ "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "Bitwardens autoutfyllingsmeny", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { @@ -3543,7 +3543,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nytt kort", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -3573,7 +3573,7 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "Importeringsfeil" }, "importErrorDesc": { "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." @@ -3737,16 +3737,16 @@ "message": "Vault data exported" }, "typePasskey": { - "message": "Passkey" + "message": "Passnøkkel" }, "accessing": { "message": "Accessing" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Innlogget!" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Passkoden vil ikke bli kopiert" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -3776,10 +3776,10 @@ "message": "Bekreft" }, "savePasskey": { - "message": "Save passkey" + "message": "Lagre passnøkkel" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Lagre passnøkkelen som en ny pålogging" }, "chooseCipherForPasskeySave": { "message": "Choose a login to save this passkey to" @@ -3842,7 +3842,7 @@ "message": "Approve the login request in your authentication app or enter a one-time passcode." }, "passcode": { - "message": "Passcode" + "message": "Passkode" }, "lastPassMasterPassword": { "message": "LastPass-hovedpassord" @@ -3882,7 +3882,7 @@ "message": "Bytt kontoer" }, "switchToAccount": { - "message": "Switch to account" + "message": "Bytt til konto" }, "activeAccount": { "message": "Aktiv konto" @@ -3903,13 +3903,13 @@ "message": "låst opp" }, "server": { - "message": "server" + "message": "tjener" }, "hostedAt": { "message": "betjent hos" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Bruk enhets- eller maskinvarenøkkel" }, "justOnce": { "message": "Kun én gang" @@ -3931,7 +3931,7 @@ "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Vil du fortsette til nettleserinnstillingene?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { @@ -3975,7 +3975,7 @@ "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "Gjør det til standarden", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { @@ -3983,7 +3983,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Passordet ble lagret!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { @@ -3991,7 +3991,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Passordet ble oppdatert!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -4002,19 +4002,19 @@ "message": "Suksess" }, "removePasskey": { - "message": "Remove passkey" + "message": "Fjern passordnøkkel" }, "passkeyRemoved": { "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Hvelvet ditt er tomt" }, "noItemsMatchSearch": { "message": "No items match your search" @@ -4043,7 +4043,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Flere innstillinger, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4053,7 +4053,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Flere innstillinger - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4063,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Vis gjenstand - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4073,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "Autoutfyll - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4086,7 +4086,7 @@ "message": "No values to copy" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Legg til i samlinger" }, "copyEmail": { "message": "Copy email" @@ -4116,7 +4116,7 @@ "message": "Error assigning target folder." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Vis gjenstander i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4126,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tilbake til $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4139,7 +4139,7 @@ "message": "Ny" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Fjern $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4149,13 +4149,13 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "Gjenstander uten mappe" }, "itemDetails": { - "message": "Item details" + "message": "Gjenstandens detaljer" }, "itemName": { - "message": "Item name" + "message": "Gjenstandens navn" }, "cannotRemoveViewOnlyCollections": { "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", @@ -4183,10 +4183,10 @@ "message": "Tilleggsinformasjon" }, "itemHistory": { - "message": "Item history" + "message": "Gjenstandshistorikk" }, "lastEdited": { - "message": "Last edited" + "message": "Nyligst redigert" }, "ownerYou": { "message": "Owner: You" @@ -4201,10 +4201,10 @@ "message": "Last opp" }, "addAttachment": { - "message": "Add attachment" + "message": "Legg til vedlegg" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Maksimal filstørrelse er 500 MB" }, "deleteAttachmentName": { "message": "Delete attachment $NAME$", @@ -4216,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "Last ned $NAME$", "placeholders": { "name": { "content": "$1", @@ -4240,10 +4240,10 @@ "message": "Filter vault" }, "filterApplied": { - "message": "One filter applied" + "message": "Ett filter er benyttet" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "$COUNT$ filtre er benyttet", "placeholders": { "count": { "content": "$1", @@ -4252,16 +4252,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "Personlige detaljer" }, "identification": { - "message": "Identification" + "message": "Identifikasjon" }, "contactInfo": { "message": "Kontaktinfo" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Last ned - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4270,17 +4270,17 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "kortnummeret slutter med", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { "message": "Legitimasjoner for innlogging" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Autentiseringsnøkkel" }, "autofillOptions": { - "message": "Autofill options" + "message": "Autoutfyllings-innstillinger" }, "websiteUri": { "message": "Website (URI)" @@ -4296,7 +4296,7 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Nettsted lagt til" }, "addWebsite": { "message": "Legg til nettsted" @@ -4305,7 +4305,7 @@ "message": "Slett nettsted" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4336,16 +4336,16 @@ "message": "Autofill on page load?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Utløpt kort" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" }, "cardDetails": { - "message": "Card details" + "message": "Kortdetaljer" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$-detaljer", "placeholders": { "brand": { "content": "$1", @@ -4357,7 +4357,7 @@ "message": "Aktiver animasjoner" }, "showAnimations": { - "message": "Show animations" + "message": "Vis animasjoner" }, "addAccount": { "message": "Legg til konto" @@ -4369,15 +4369,15 @@ "message": "Data" }, "passkeys": { - "message": "Passkeys", + "message": "Passnøkler", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "Passord", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Logg inn med passnøkkel", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { @@ -4402,16 +4402,16 @@ } }, "addField": { - "message": "Add field" + "message": "Legg til felt" }, "add": { "message": "Legg til" }, "fieldType": { - "message": "Field type" + "message": "Felttype" }, "fieldLabel": { - "message": "Field label" + "message": "Feltetikett" }, "textHelpText": { "message": "Use text fields for data like security questions" @@ -4441,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Slett $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4450,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ er lagt til", "placeholders": { "label": { "content": "$1", @@ -4572,7 +4572,7 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "Gjenstandens plassering" }, "fileSend": { "message": "File Send" @@ -4587,7 +4587,7 @@ "message": "Text Sends" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden har fått et nytt utseende!" }, "bitwardenNewLookDesc": { "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" @@ -4608,16 +4608,16 @@ "message": "Enterprise policy requirements have been applied to this setting" }, "sshPrivateKey": { - "message": "Private key" + "message": "Privat nøkkel" }, "sshPublicKey": { - "message": "Public key" + "message": "Offentlig nøkkel" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Fingeravtrykk" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Nøkkeltype" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4638,22 +4638,22 @@ "message": "Minimum custom timeout is 1 minute." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Ytterligere innhold er tilgjengelig" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, "showCharacterCount": { - "message": "Show character count" + "message": "Vis tegntelleren" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Skjul tegntelleren" }, "itemsInTrash": { - "message": "Items in trash" + "message": "Gjenstander i papirkurven" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Ingen gjenstander i papirkurven" }, "noItemsInTrashDesc": { "message": "Items you delete will appear here and be permanently deleted after 30 days" @@ -4709,11 +4709,11 @@ "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Vil du lagre påloggingen i Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Mellomrom", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { @@ -4721,15 +4721,15 @@ "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Baklengs apostrof", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Utropstegn", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Alfakrøll", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { @@ -4737,11 +4737,11 @@ "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Dollartegn", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Prosenttegn", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { @@ -4749,7 +4749,7 @@ "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "Prosenttegn", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { @@ -4757,23 +4757,23 @@ "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Venstre parantes", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Høyre parantes", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Understrek", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Bindestrek", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Plusstegn", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { @@ -4781,19 +4781,19 @@ "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Venstre krøllparentes", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Høyre krøllparentes", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Venstre firkantparantes", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Høyre firkantparantes", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { @@ -4801,69 +4801,69 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Skråstrek bakover", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Kolon", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Semikolon", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Hermetegn", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Apostrofe", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Mindre enn", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Større enn", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Komma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Tidsperiode", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Spørsmålstegn", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Skråstrek", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Små bokstaver" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Store bokstaver" }, "generatedPassword": { - "message": "Generated password" + "message": "Generert passord" }, "compactMode": { - "message": "Compact mode" + "message": "Kompakt modus" }, "beta": { "message": "Beta" }, "importantNotice": { - "message": "Important notice" + "message": "Viktig melding" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Sett opp 2-trinnspålogging" }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." @@ -4884,7 +4884,7 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nei, det gjør jeg ikke" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Yes, I can reliably access my email" @@ -4893,15 +4893,15 @@ "message": "Turn on two-step login" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Endre kontoens E-postadresse" }, "extensionWidth": { - "message": "Extension width" + "message": "Utvidelsens bredde" }, "wide": { - "message": "Wide" + "message": "Bred" }, "extraWide": { - "message": "Extra wide" + "message": "Ekstra bred" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index f11f0860d19..dd6a2286c4a 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -290,7 +290,7 @@ "message": "前往帮助中心吗?" }, "continueToHelpCenterDesc": { - "message": "访问帮助中心了解更多如何使用 Bitwarden 的信息。" + "message": "在帮助中心进一步了解如何使用 Bitwarden。" }, "continueToBrowserExtensionStore": { "message": "前往浏览器扩展商店吗?" @@ -834,7 +834,7 @@ "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来截取此网站的验证器二维码,或者手动复制并粘贴密钥到此字段。" }, "learnMoreAboutAuthenticators": { - "message": "了解更多关于验证器的信息" + "message": "进一步了解验证器" }, "copyTOTP": { "message": "复制验证器密钥 (TOTP)" @@ -1514,10 +1514,10 @@ "message": "不完整或不信任的网站可以利用页面加载时的自动填充功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "了解更多关于风险的信息" + "message": "进一步了解风险" }, "learnMoreAboutAutofill": { - "message": "了解更多关于自动填充的信息" + "message": "进一步了解自动填充" }, "defaultAutoFillOnPageLoad": { "message": "登录项目的默认自动填充设置" @@ -2318,14 +2318,14 @@ "message": "一个组织策略正影响您的所有权选项。" }, "personalOwnershipPolicyInEffectImports": { - "message": "组织策略已阻止将项目导入您的个人密码库。" + "message": "某个组织策略已阻止将项目导入您的个人密码库。" }, "domainsTitle": { "message": "域名", "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "已屏蔽的域名" + "message": "屏蔽域名" }, "excludedDomains": { "message": "排除域名" @@ -2364,7 +2364,7 @@ } }, "blockedDomainsSavedSuccess": { - "message": "已屏蔽的域名更改已保存" + "message": "屏蔽域名更改已保存" }, "excludedDomainsSavedSuccess": { "message": "排除域名更改已保存" @@ -4875,7 +4875,7 @@ "message": "稍后提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "您能可正常访问您的电子邮箱 $EMAIL$ 吗?", + "message": "您可以正常访问您的电子邮箱 $EMAIL$ 吗?", "placeholders": { "email": { "content": "$1", diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index 3348cf1910c..0f73ccd6619 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -135,11 +135,12 @@ 每个人都应该拥有的保持在线安全的工具 使用 Bitwarden 是免费的,没有广告,不会出售数据。Bitwarden 相信每个人都应该拥有保持在线安全的能力。高级计划提供了对高级功能的访问。 -使用 BITWARDEN 为您的团队提供支持 +使用 Bitwarden 为您的团队提供支持 团队计划和企业计划具有专业的商业功能。例如 SSO 集成、自托管、目录集成,以及 SCIM 配置、全局策略、API 访问、事件日志等。 使用 Bitwarden 保护您的劳动成果,并与同事共享敏感信息。 + 选择 Bitwarden 的更多理由: 世界级加密 @@ -164,7 +165,7 @@ Bitwarden 的翻译涵盖 60 多种语言,由全球社区使用 Crowdin 翻译 从任何浏览器、移动设备或桌面操作系统中安全地访问和共享 Bitwarden 密码库中的敏感数据。 Bitwarden 保护的不仅仅是密码 -Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息! +Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 进一步了解 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev! 无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 From a8ab649fa6846dda01ce6651103e22a6515e6f29 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:40:44 +0100 Subject: [PATCH 146/270] Autosync the updated translations (#12795) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ar/messages.json | 74 ++++++++++++++++ apps/web/src/locales/az/messages.json | 74 ++++++++++++++++ apps/web/src/locales/be/messages.json | 74 ++++++++++++++++ apps/web/src/locales/bg/messages.json | 74 ++++++++++++++++ apps/web/src/locales/bn/messages.json | 74 ++++++++++++++++ apps/web/src/locales/bs/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ca/messages.json | 74 ++++++++++++++++ apps/web/src/locales/cs/messages.json | 74 ++++++++++++++++ apps/web/src/locales/cy/messages.json | 74 ++++++++++++++++ apps/web/src/locales/da/messages.json | 74 ++++++++++++++++ apps/web/src/locales/de/messages.json | 74 ++++++++++++++++ apps/web/src/locales/el/messages.json | 74 ++++++++++++++++ apps/web/src/locales/en_GB/messages.json | 74 ++++++++++++++++ apps/web/src/locales/en_IN/messages.json | 74 ++++++++++++++++ apps/web/src/locales/eo/messages.json | 74 ++++++++++++++++ apps/web/src/locales/es/messages.json | 74 ++++++++++++++++ apps/web/src/locales/et/messages.json | 74 ++++++++++++++++ apps/web/src/locales/eu/messages.json | 74 ++++++++++++++++ apps/web/src/locales/fa/messages.json | 74 ++++++++++++++++ apps/web/src/locales/fi/messages.json | 74 ++++++++++++++++ apps/web/src/locales/fil/messages.json | 74 ++++++++++++++++ apps/web/src/locales/fr/messages.json | 74 ++++++++++++++++ apps/web/src/locales/gl/messages.json | 74 ++++++++++++++++ apps/web/src/locales/he/messages.json | 74 ++++++++++++++++ apps/web/src/locales/hi/messages.json | 74 ++++++++++++++++ apps/web/src/locales/hr/messages.json | 74 ++++++++++++++++ apps/web/src/locales/hu/messages.json | 74 ++++++++++++++++ apps/web/src/locales/id/messages.json | 74 ++++++++++++++++ apps/web/src/locales/it/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ja/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ka/messages.json | 74 ++++++++++++++++ apps/web/src/locales/km/messages.json | 74 ++++++++++++++++ apps/web/src/locales/kn/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ko/messages.json | 74 ++++++++++++++++ apps/web/src/locales/lv/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ml/messages.json | 74 ++++++++++++++++ apps/web/src/locales/mr/messages.json | 74 ++++++++++++++++ apps/web/src/locales/my/messages.json | 74 ++++++++++++++++ apps/web/src/locales/nb/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ne/messages.json | 74 ++++++++++++++++ apps/web/src/locales/nl/messages.json | 74 ++++++++++++++++ apps/web/src/locales/nn/messages.json | 74 ++++++++++++++++ apps/web/src/locales/or/messages.json | 74 ++++++++++++++++ apps/web/src/locales/pl/messages.json | 74 ++++++++++++++++ apps/web/src/locales/pt_BR/messages.json | 74 ++++++++++++++++ apps/web/src/locales/pt_PT/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ro/messages.json | 74 ++++++++++++++++ apps/web/src/locales/ru/messages.json | 74 ++++++++++++++++ apps/web/src/locales/si/messages.json | 74 ++++++++++++++++ apps/web/src/locales/sk/messages.json | 74 ++++++++++++++++ apps/web/src/locales/sl/messages.json | 74 ++++++++++++++++ apps/web/src/locales/sr/messages.json | 74 ++++++++++++++++ apps/web/src/locales/sr_CS/messages.json | 74 ++++++++++++++++ apps/web/src/locales/sv/messages.json | 74 ++++++++++++++++ apps/web/src/locales/te/messages.json | 74 ++++++++++++++++ apps/web/src/locales/th/messages.json | 74 ++++++++++++++++ apps/web/src/locales/tr/messages.json | 74 ++++++++++++++++ apps/web/src/locales/uk/messages.json | 74 ++++++++++++++++ apps/web/src/locales/vi/messages.json | 74 ++++++++++++++++ apps/web/src/locales/zh_CN/messages.json | 106 +++++++++++++++++++---- apps/web/src/locales/zh_TW/messages.json | 74 ++++++++++++++++ 62 files changed, 4604 insertions(+), 16 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index f86b6ab497b..1dd2d62f2a5 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Teken asb. weer aan." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Teken asb. weer aan. Indien u ander Bitwarden-toepassings gebruik, teken daarop ook weer uit en aan." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Toestel" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Keur versoek goed" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Geen toestelversoeke" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index ed843a62cd1..2c2549e2f1e 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "بَدْء تسجيل الدخول" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "الرجاء تسجيل الدخول مرة أخرى." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "الجهاز" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index b8111a0e997..b140ae320be 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Kimliyinizi doğrulayın" }, + "whatIsADevice": { + "message": "Cihaz nədir?" + }, + "aDeviceIs": { + "message": "Cihaz, giriş etdiyiniz Bitwarden tətbiqinin unikal quraşdırmasıdır. Yenidən quraşdırılması, tətbiq datasının təmizlənməsi və ya çərəzlərin təmizlənməsi, cihazın bir neçə dəfə görünməsinə səbəb ola bilər." + }, "logInInitiated": { "message": "Giriş etmə başladıldı" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Lütfən yenidən giriş edin." }, + "currentSession": { + "message": "Hazırkı seans" + }, + "requestPending": { + "message": "Tələb gözlənir" + }, "logBackInOthersToo": { "message": "Lütfən yenidən giriş edin. Digər Bitwarden tətbiqlərini istifadə edirsinizsə, onlardan da çıxış edib təkrar giriş etməlisiniz." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 dəvət haqqınız var." + }, "userUsingTwoStep": { "message": "Bu istifadəçinin hesabını qorumaq üçün iki addımlı giriş istifadə edilir." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Giriş statusu" + }, + "firstLogin": { + "message": "İlk giriş" + }, + "trusted": { + "message": "Güvənli" + }, "creatingAccountOn": { "message": "Hesab yaradılır" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "İdarə edilən istifadəçilərə həm də \"Hesab geri qaytarılmasını idarə et\" icazəsi verilməlidir" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Tələbi təsdiqlə" }, + "deviceApproved": { + "message": "Cihaz təsdiqləndi" + }, + "deviceRemoved": { + "message": "Cihaz silindi" + }, + "removeDevice": { + "message": "Cihazı sil" + }, + "removeDeviceConfirmation": { + "message": "Bu cihazı silmək istədiyinizə əminsiniz?" + }, "noDeviceRequests": { "message": "Heç bir cihaz tələbi yoxdur" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Üzvləri çıxart" }, + "devices": { + "message": "Cihazlar" + }, + "deviceListDescription": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib. Tanımadığınız cihaz varsa, onu silin." + }, + "deviceListDescriptionTemp": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib." + }, "claimedDomains": { "message": "Götürülmüş domenlər" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Təşkilat abunəliyi yenidən başladıldı" + }, + "restartSubscription": { + "message": "Abunəliyinizi yenidən başladın" + }, + "suspendedManagedOrgMessage": { + "message": "Kömək üçün $PROVIDER$ ilə əlaqə saxlayın.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index ea705b38b4e..2a037c5b11c 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Ініцыяваны ўваход" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Калі ласка, увайдзіце паўторна." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Калі ласка, увайдзіце паўторна. Калі вы выкарыстоўваеце іншыя праграмы Bitwarden, выйдзіце з іх, а потым увайдзіце яшчэ раз." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Гэты карыстальнік выкарыстоўвае двухэтапны ўваход для абароны свайго ўліковага запісу." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Прылада" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для кіравання карыстальнікамі неабходна даць дазвол на кіраванне аднаўленнем уліковым запісам" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Ухваліць запыт" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Няма запытаў ад прылады" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 1f5fb8d8c7e..531cfd18a42 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Потвърдете самоличността си" }, + "whatIsADevice": { + "message": "Какво представлява едно устройство?" + }, + "aDeviceIs": { + "message": "Едно устройство наричаме инсталацията на приложението Битуорден, където сте се вписали. Ако преинсталирате приложението, изчистите данните му или изтриете бисквитките си, това устройство може да се появи няколко пъти в списъка." + }, "logInInitiated": { "message": "Вписването е стартирано" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Впишете се отново." }, + "currentSession": { + "message": "Текуща сесия" + }, + "requestPending": { + "message": "Чакаща заявка" + }, "logBackInOthersToo": { "message": "Впишете се отново. Ако използвате и други приложения на Битуорден, впишете се отново и в тях." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Имате 1 оставаща покана." + }, "userUsingTwoStep": { "message": "Този потребител използва двустепенна защита за достъп." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Състояние на вписването" + }, + "firstLogin": { + "message": "Първо вписване" + }, + "trusted": { + "message": "Доверено" + }, "creatingAccountOn": { "message": "Създаване на регистрация в" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Управлението на потребителите трябва да бъде дадено заедно с разрешението за Управление на възстановяването на регистрации" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Одобряване на заявката" }, + "deviceApproved": { + "message": "Устройството е одобрено" + }, + "deviceRemoved": { + "message": "Устройството е премахнато" + }, + "removeDevice": { + "message": "Премахване на устройството" + }, + "removeDeviceConfirmation": { + "message": "Наистина ли искате да премахнете това устройство?" + }, "noDeviceRequests": { "message": "Няма заявки от устройства" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Премахване на членовете" }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Вие сте се вписали във всяко от устройствата по-долу. Ако не разпознавате някое от тях, премахнете го сега." + }, + "deviceListDescriptionTemp": { + "message": "Вашият акаунт е вписан във всяко от устройствата по-долу." + }, "claimedDomains": { "message": "Присвоени домейни" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Абонаментът на организацията е рестартиран" + }, + "restartSubscription": { + "message": "Рестартирайте абонамента си" + }, + "suspendedManagedOrgMessage": { + "message": "Свържете се с $PROVIDER$ за помощ.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 56a04f3d870..b215e56e19a 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 3e9c4525b04..5f2605e71bc 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 1cf16dcc0fd..30649f3296d 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verificació de la vostra identitat" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "S'ha iniciat la sessió" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Torneu a iniciar sessió." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Torneu a iniciar la sessió. Si esteu utilitzant altres aplicacions Bitwarden, tanqueu-les i torneu-les a obrir també." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Aquest usuari fa servir l'inici de sessió en dues passes per protegir el seu compte." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositiu" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "L'administració d'usuaris també ha d'estar habilitada amb el permís de recuperació del compte de gestió" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprova la sol·licitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hi ha sol·licituds de dispositiu" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 64ce764de82..7c8978fac80 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Ověřte svou totožnost" }, + "whatIsADevice": { + "message": "Co je to zařízení?" + }, + "aDeviceIs": { + "message": "Zařízení je jedinečná instalace aplikace Bitwarden, ve které jste se přihlásili. Přeinstalování, vymazání dat aplikace nebo vymazání souborů cookie může vést k tomu, že se zařízení objeví vícekrát." + }, "logInInitiated": { "message": "Bylo zahájeno přihlášení" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Přihlaste se znovu." }, + "currentSession": { + "message": "Aktuální relace" + }, + "requestPending": { + "message": "Čekající požadavek" + }, "logBackInOthersToo": { "message": "Přihlaste se znovu. Používáte-li jiné aplikace Bitwardenu, přihlaste se znovu i v nich." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Zbývá Vám 1 pozvánka." + }, "userUsingTwoStep": { "message": "Tento uživatel používá pro ochranu svého účtu dvoufázové přihlášení." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Zařízení" }, + "loginStatus": { + "message": "Stav přihlášení" + }, + "firstLogin": { + "message": "První přihlášení" + }, + "trusted": { + "message": "Důvěryhodný" + }, "creatingAccountOn": { "message": "Vytváření účtu na" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "S oprávněním spravovat obnovení hesla musí být povolena také správa uživatelů" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Schválit žádost" }, + "deviceApproved": { + "message": "Zařízení schváleno" + }, + "deviceRemoved": { + "message": "Zařízení odebráno" + }, + "removeDevice": { + "message": "Odebrat zařízení" + }, + "removeDeviceConfirmation": { + "message": "Opravdu chcete odebrat toto zařízení?" + }, "noDeviceRequests": { "message": "Žádné žádosti ze zařízení" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Odebrat členy" }, + "devices": { + "message": "Zařízení" + }, + "deviceListDescription": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení. Pokud zařízení nepoznáváte, odeberte jej nyní." + }, + "deviceListDescriptionTemp": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení." + }, "claimedDomains": { "message": "Uplatněné domény" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Předplatné organizace bylo restartováno" + }, + "restartSubscription": { + "message": "Restartovat Vaše předplatné" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktujte $PROVIDER$ pro pomoc.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index c0c39c91d30..ed8a03eb8b0 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index f71e1feeb3b..9731c344e6e 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Bekræft din identitet" }, + "whatIsADevice": { + "message": "Hvad er en enhed?" + }, + "aDeviceIs": { + "message": "En enhed er en unik installation af Bitwarden-appen, hvor man har logget ind. Geninstallation, rydning af app-data eller rydning af cookies kan medføre, at en enhed vises op flere gange." + }, "logInInitiated": { "message": "Indlogning påbegyndt" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Log ind igen." }, + "currentSession": { + "message": "Aktuel session" + }, + "requestPending": { + "message": "Anmodning afventer" + }, "logBackInOthersToo": { "message": "Log ind igen. Bruger du andre Bitwarden-applikationer, så log også ud og ind igen på disse." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Der er 1 invitation tilbage." + }, "userUsingTwoStep": { "message": "Denne bruger benytter totrins-login for at beskytte kontoen." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Enhed" }, + "loginStatus": { + "message": "Indlogningsstatus" + }, + "firstLogin": { + "message": "Første login" + }, + "trusted": { + "message": "Betroet" + }, "creatingAccountOn": { "message": "Opretter konto på" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Håndtér brugere skal også tildeles med tilladelsen håndtér kontogendannelse" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Godkend anmodning" }, + "deviceApproved": { + "message": "Enhed godkendt" + }, + "deviceRemoved": { + "message": "Enhed fjernet" + }, + "removeDevice": { + "message": "Fjern enhed" + }, + "removeDeviceConfirmation": { + "message": "Sikker på, at denne enhed skal fjernes?" + }, "noDeviceRequests": { "message": "Ingen enhedsanmodninger" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Fjern medlemmer" }, + "devices": { + "message": "Enheder" + }, + "deviceListDescription": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor. Genkendes en enhed ikke, fjern den nu." + }, + "deviceListDescriptionTemp": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor." + }, "claimedDomains": { "message": "Registrerede domæner" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisationsabonnement genstartet" + }, + "restartSubscription": { + "message": "Genstart abonnementet" + }, + "suspendedManagedOrgMessage": { + "message": "Kontakt $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 93422471457..540db2dec05 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifiziere deine Identität" }, + "whatIsADevice": { + "message": "Was ist ein \"Gerät\"?" + }, + "aDeviceIs": { + "message": "Ein \"Gerät\" ist eine einzigartige Installation der Bitwarden-App, in der du dich angemeldet hast. Eine Neuinstallation, das Löschen von App-Daten oder das Löschen von Cookies könnte dazu führen, dass ein \"Gerät\" mehrfach erscheint." + }, "logInInitiated": { "message": "Anmeldung eingeleitet" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Bitte melden Sie sich erneut an." }, + "currentSession": { + "message": "Aktuelle Sitzung" + }, + "requestPending": { + "message": "Anfrage ausstehend" + }, "logBackInOthersToo": { "message": "Bitte melden Sie sich wieder an. Wenn Sie andere Bitwarden-Anwendungen verwenden, melden Sie sich auch dort ab und wieder neu an." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Du hast noch eine Einladung übrig." + }, "userUsingTwoStep": { "message": "Dieser Benutzer hat sein Konto mit einer Zwei-Faktor-Authentifizierung geschützt." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Gerät" }, + "loginStatus": { + "message": "Anmeldestatus" + }, + "firstLogin": { + "message": "Erste Anmeldung" + }, + "trusted": { + "message": "Vertrauenswürdig" + }, "creatingAccountOn": { "message": "Konto wird erstellt bei" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiere das Kundenerfolgsteam", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Benutzer verwalten\" muss zusammen mit der \"Kontowiederherstellung verwalten\"-Berechtigung erteilt werden" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Anfrage genehmigen" }, + "deviceApproved": { + "message": "\"Gerät\" genehmigt" + }, + "deviceRemoved": { + "message": "\"Gerät\" entfernt" + }, + "removeDevice": { + "message": "\"Gerät\" entfernen" + }, + "removeDeviceConfirmation": { + "message": "Möchtest du das \"Gerät\" wirklich entfernen?" + }, "noDeviceRequests": { "message": "Keine Geräteanfragen" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Mitglieder entfernen" }, + "devices": { + "message": "\"Geräte\"" + }, + "deviceListDescription": { + "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt. Wenn du ein \"Gerät\" nicht wiedererkennst, dann entferne es jetzt." + }, + "deviceListDescriptionTemp": { + "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt." + }, "claimedDomains": { "message": "Beanspruchte Domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisations-Abonnement neu gestartet" + }, + "restartSubscription": { + "message": "Abonnement neu starten" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktiere $PROVIDER$ für Hilfe.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 42d24fae72d..adfb86665ab 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Επαληθεύστε την ταυτότητά σας" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Η σύνδεση ξεκίνησε" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Παρακαλούμε συνδεθείτε ξανά." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Παρακαλούμε συνδεθείτε ξανά. Εάν χρησιμοποιείτε άλλες εφαρμογές Bitwarden, αποσυνδεθείτε και επιστρέψτε σε αυτές επίσης." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Αυτός ο χρήστης χρησιμοποιεί τρόπο σύνδεσης δύο βημάτων για να προστατεύσει το λογαριασμό του." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Συσκευή" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Δημιουργία λογαριασμού στο" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Έγκριση αιτήματος" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index f37aa8150cf..a2002039ab9 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index ec67bb192c9..2564812802a 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 0170c225c03..21eba5a4a99 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Bonvolu saluti refoje." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Bonvolu saluti refoje. Se vi uzas aliajn programojn de Bitwarden, adiaŭu kaj ankaŭ salutu ilin." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ĉi tiu uzanto uzas du-paŝan ensaluton por protekti sian konton." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Aparato" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 3c3d7bb135d..1393fe59b27 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifica tu identidad" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Inicio de sesión en proceso" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Por favor, vuelve a acceder." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, vuelve a acceder. Si estás utilizando otras aplicaciones de Bitwarden, cierra sesión y vuelva a acceder en ellas también." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Este usuario está usando autenticación de dos pasos para proteger su cuenta." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creando una cuenta en" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "El permiso \"gestionar usuarios\" también debe ser otorgado junto con el permiso \"gestionar cuenta\"" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprobar solicitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hay solicitudes de dispositivo" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 700c748add8..9122e4b8778 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Kinnitage oma Identiteet" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Sisselogimine käivitatud" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Palun logi uuesti sisse." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Palun logi uuesti sisse. Kui kasutad teisi Bitwardeni rakendusi, pead ka nendes uuesti sisse logima." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Sellel kasutajal on kaheastmeline kinnitamine sisse lülitatud." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Seade" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Konto loomise asukoht" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 4c7719ada13..576d8207aae 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Saioa hastea martxan da" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Mesedez, hasi saioa berriro." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mesedez, hasi saioa berriro. Bitwardenen beste aplikazioren bat erabiltzen ari bazara, itxi saioa eta hasi saioa berriro aplikazio horretan ere." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Erabiltzaile hau bi urratseko saio hasiera erabiltzen ari da bere kontua babesteko." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Gailua" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index fe0ba063a7b..589a019973f 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "ورود به سیستم آغاز شد" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "لطفاً دوباره وارد شوید." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "لطفاً دوباره وارد شوید. اگر از سایر برنامه‌های Bitwarden استفاده می‌کنید، از سیستم خارج شوید و دوباره به آن‌ها وارد شوید." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "این کاربر از ورود دو مرحله ای برای محافظت از حساب خود استفاده می‌کند." }, @@ -3765,6 +3780,15 @@ "device": { "message": "دستگاه" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "مدیر کاربران همچنین باید مجوز مدیریت بازیابی حساب کاربری را داشته باشد" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "درخواست تأیید" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "بدون درخواست دستگاه" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 234cfb0ee3e..b6825bed162 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Vahvista henkilöllisyytesi" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Kirjautuminen aloitettu" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Kirjaudu sisään uudelleen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Kirjaudu uudelleen sisään. Jos käytät muita Bitwarden-sovelluksia, kirjaudu myös niihin uudelleen." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Käyttäjä on suojannut tilinsä kaksivaiheisella kirjautumisella." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Laite" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Luodaan tili palvelimelle" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Tilien palautusavun hallinta\" -oikeuden kanssa on myönnettävä myös \"Käyttäjien hallinta\" -oikeus." }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Hyväksy pyyntö" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Laitepyyntöjä ei ole" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Poista jäsenet" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index f5a9b078984..9dcacdb44f8 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Sinimulan ang pag-log in" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Mangyaring mag-log in ulit." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mangyaring mag-log in ulit. Kung gumagamit ka ng iba pang mga aplikasyon pang-Bitwarden, mag-log out at log in rin ulit sa mga iyon." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Gumagamit ang user na ito ng dalawang hakbang na pag login upang maprotektahan ang kanilang account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index e6c402293f8..933bfd8759f 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Vérifiez votre Identité" }, + "whatIsADevice": { + "message": "Qu'est-ce qu'un appareil ?" + }, + "aDeviceIs": { + "message": "Un appareil est une installation unique de l'application Bitwarden où vous vous êtes connecté. Réinstaller, effacer les données de l'application ou effacer vos cookies peut entraîner l'apparition d'un appareil plusieurs fois." + }, "logInInitiated": { "message": "Connexion initiée" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Veuillez vous reconnecter." }, + "currentSession": { + "message": "Session en cours" + }, + "requestPending": { + "message": "Demande en attente" + }, "logBackInOthersToo": { "message": "Veuillez vous reconnecter. Si vous utilisez d'autres applications Bitwarden, déconnectez-vous et reconnectez-vous également de celles-ci." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Cet utilisateur utilise l'authentification à deux facteurs pour protéger son compte." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Appareil" }, + "loginStatus": { + "message": "Statut de connexion" + }, + "firstLogin": { + "message": "Première connexion" + }, + "trusted": { + "message": "Approuvé" + }, "creatingAccountOn": { "message": "Création du compte sur" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Erreur de déchiffrement" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden n'a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacter 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": "pour éviter des pertes de données supplémentaires.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gérer les utilisateurs exige également d'avoir l'autorisation de gérer la restauration de compte" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approuver la demande" }, + "deviceApproved": { + "message": "Appareil approuvé" + }, + "deviceRemoved": { + "message": "Appareil supprimé" + }, + "removeDevice": { + "message": "Supprimer l'appareil" + }, + "removeDeviceConfirmation": { + "message": "Êtes-vous sûr de vouloir supprimer cet appareil ?" + }, "noDeviceRequests": { "message": "Aucune demande de l'appareil" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Retirer des membres" }, + "devices": { + "message": "Appareils" + }, + "deviceListDescription": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous. Si vous ne reconnaissez pas un appareil, supprimez-le maintenant." + }, + "deviceListDescriptionTemp": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous." + }, "claimedDomains": { "message": "Domaines réclamés" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "L'abonnement à l'organisation a été redémarré" + }, + "restartSubscription": { + "message": "Redémarrez votre abonnement" + }, + "suspendedManagedOrgMessage": { + "message": "Contactez $PROVIDER$ pour obtenir de l'aide.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 986350b0637..3e2c1161067 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 9c4ef530721..6db8255c46e 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "אנא התחבר שוב." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "אנא התחבר שוב. אם אתה משתמש באפליקציות נוספות של Bitwarden, סגור את החיבור והתחבר שוב גם באפליקציות הללו." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "משתמש זה הפעיל כניסה דו שלבית כדי להגן על חשבונו." }, @@ -3765,6 +3780,15 @@ "device": { "message": "מכשיר" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 82a0c12a390..25f7bc6e6f7 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 7b77ae9d58f..610b63d68c1 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Potvrdi svoj identitet" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Pokrenuta prijava" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Molimo, ponovno se prijavi." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Molimo, ponovno se prijavi. Ako koristiš druge aplikacije Bitwarden i u njima napravi odjavu/prijavu." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ovaj korisnik upotrebljava prijavu u dva koraka za zaštitu svog računa." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Uređaj" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Stvaranje računa na" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Upravljanje korisnicima mora također biti uključeno s dozvolom za Upravljanje ponovnim postavljanjem lozinke" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Odobri zahtjev" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Nema zahtjeva na čekanju" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Ukloni članove" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Potvrđene domene" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index ecd7db04ca1..163355b20d9 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Személyazonosság ellenőrzése" }, + "whatIsADevice": { + "message": "Mi az eszköz?" + }, + "aDeviceIs": { + "message": "Az eszköz a Bitwarden alkalmazás egyedi telepítése, amelyre bejelentkeztünk. Az alkalmazás adatok újratelepítése, törlése vagy a sütik törlése azt eredményezheti, hogy egy eszköz többször is megjelenhet." + }, "logInInitiated": { "message": "A bejelentkezés elindításra került." }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Ismételten be kell jelentkezni." }, + "currentSession": { + "message": "Jelenlegi munkamenet" + }, + "requestPending": { + "message": "Függőben lévő kérelem" + }, "logBackInOthersToo": { "message": "Ismételten be kell jelentkezni. Ha másik Bitwarden alkalmazásokat használunk, ott is jelentkezzünk ki és ismételten be." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 meghívó maradt." + }, "userUsingTwoStep": { "message": "Ez a felhasználó kétlépcsős bejelentkezést használ fiókja védelmére." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Eszköz" }, + "loginStatus": { + "message": "Bejelentkezési állapot" + }, + "firstLogin": { + "message": "Első bejelentkezés" + }, + "trusted": { + "message": "Megbízható" + }, "creatingAccountOn": { "message": "Fiók létrehozása:" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A felhasználók kezelését engedélyezni kell a Jelszó visszaállításának kezelése jogosultsággal is." }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Kérés megerősítése" }, + "deviceApproved": { + "message": "Az eszköz jóváhagyásra került." + }, + "deviceRemoved": { + "message": "Az eszköz eltávolításra került." + }, + "removeDevice": { + "message": "Eszköz eltávolítása" + }, + "removeDeviceConfirmation": { + "message": "Biztos eltávolításra kerüljön ez az eszköz?" + }, "noDeviceRequests": { "message": "Nincs eszköz kérés" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Tagok eltávolítása" }, + "devices": { + "message": "Eszközök" + }, + "deviceListDescription": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett. Ha nem ismerünk fel egy eszközt, távolítsuk el most." + }, + "deviceListDescriptionTemp": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett." + }, "claimedDomains": { "message": "Igényelt tartományok" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "A szervezeti feliratkozás újraindult." + }, + "restartSubscription": { + "message": "Feliratkozás újra indítása" + }, + "suspendedManagedOrgMessage": { + "message": "Segítséget $PROVIDER$ szolgáltatótól kaphatunk.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 7517cac5ae5..3b861fb16da 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Harap masuk kembali." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Harap masuk kembali. Jika Anda menggunakan aplikasi Bitwarden lain, keluarlah dan masuk kembali ke sana juga." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Pengguna ini menggunakan proses masuk dua langkah untuk melindungi akun mereka." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Perangkat" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 0154a3c8c78..8ce01a4737d 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifica la tua identità" }, + "whatIsADevice": { + "message": "Cos'è un dispositivo?" + }, + "aDeviceIs": { + "message": "Un dispositivo è un'installazione unica dell'app Bitwarden con la quale hai effettuato l'accesso. Reinstallazione, eliminazione dei dati dell'app o cancellazione dei cookie potrebbero causare la comparsa di un dispositivo più volte." + }, "logInInitiated": { "message": "Login avviato" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Accedi di nuovo." }, + "currentSession": { + "message": "Sessione attuale" + }, + "requestPending": { + "message": "Richiesta in attesa" + }, "logBackInOthersToo": { "message": "Accedi di nuovo. Se stai usando Bitwarden su altre app, esci e rientra anche in quelle." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Hai ancora 1 invito." + }, "userUsingTwoStep": { "message": "Questo utente usa la verifica in due passaggi per proteggere il suo account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Stato accesso" + }, + "firstLogin": { + "message": "Primo accesso" + }, + "trusted": { + "message": "Di fiducia" + }, "creatingAccountOn": { "message": "Creazione account su" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gestisci utenti deve essere abilitato con il permesso di gestire il ripristino delle password" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approva richiesta" }, + "deviceApproved": { + "message": "Dispositivo approvato" + }, + "deviceRemoved": { + "message": "Dispositivo rimosso" + }, + "removeDevice": { + "message": "Rimuovi dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Sei sicuro di voler rimuovere il dispositivo?" + }, "noDeviceRequests": { "message": "Nessuna richiesta da approvare" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Dispositivi" + }, + "deviceListDescription": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto. Se non riconosci un dispositivo, rimuovilo ora." + }, + "deviceListDescriptionTemp": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Abbonamento organizzazione riavviato" + }, + "restartSubscription": { + "message": "Riavvia il tuo abbonamento" + }, + "suspendedManagedOrgMessage": { + "message": "Contatta $PROVIDER$ per assistenza.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 6b3b6f13b46..44213d143aa 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "本人確認" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "ログイン開始" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "ログインし直してください" }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "ログインし直してください。他のBitwardenのアプリを使用している場合、同様にログインし直してください。" }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "このユーザーはアカウントを保護するため二段階認証を利用しています。" }, @@ -3765,6 +3780,15 @@ "device": { "message": "デバイス" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "アカウント作成:" }, @@ -5655,6 +5679,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "ユーザーを管理するには、アカウントのリカバリ管理権限を付与する必要があります。" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "リクエストを承認" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "デバイスリクエストはありません" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "メンバーを削除" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index cc4dc222103..610bc0f6d87 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "ავტორიზაცია დაწყებულია" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index e1fd4cc9ac9..215bde966ad 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ. ನೀವು ಇತರ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬಳಸುತ್ತಿದ್ದರೆ ಲಾಗ್ಔಟ್ ಮಾಡಿ ಮತ್ತು ಅವುಗಳಿಗೆ ಹಿಂತಿರುಗಿ." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "ಈ ಬಳಕೆದಾರರು ತಮ್ಮ ಖಾತೆಯನ್ನು ರಕ್ಷಿಸಲು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಬಳಸುತ್ತಿದ್ದಾರೆ." }, @@ -3765,6 +3780,15 @@ "device": { "message": "ಡಿವೈಸ್" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 7966191f530..0cd1302ea2f 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "다시 로그인해 주세요." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "다시 로그인해 주세요. 다른 Bitwarden 앱을 사용 중인 경우 해당 앱에서도 다시 로그인해야 합니다." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "이 사용자는 계정을 보호하기 위해 2단계 로그인을 사용하고 있습니다." }, @@ -3765,6 +3780,15 @@ "device": { "message": "기기" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 0b830e6dc9a..51ca6a0f798 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Jāapliecina sava identitāte" }, + "whatIsADevice": { + "message": "Kas ir ierīce?" + }, + "aDeviceIs": { + "message": "Ierīce ir atsevišķa uzstādīta Bitwarde lietotne, kurā ir veikta pieteikšanās. Atkārtota uzstādīšana, lietotnes datu vai sīkdatņu notīrīšana var beigties ar ierīces vairākkārtīgu parādīšanos." + }, "logInInitiated": { "message": "Uzsākta pieteikšanās" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Lūgums pieteikties atkārtoti." }, + "currentSession": { + "message": "Pašreizējā sesija" + }, + "requestPending": { + "message": "Pieprasījums ir apstrādē" + }, "logBackInOthersToo": { "message": "Lūgums pieteikties atkārtoti. Ja tiek izmantotas citas Bitwarden lietotnes, ir nepieciešams atteikties un atkārtoti pieteikties arī tajās." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Tev ir atlicis 1 uzaicinājums." + }, "userUsingTwoStep": { "message": "Šis lietotājs izmanto divpakāpju pieteikšanos, lai aizsargātu savu kontu." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Ierīce" }, + "loginStatus": { + "message": "Pieteikšanās stāvoklis" + }, + "firstLogin": { + "message": "Pirmā pieteikšanās" + }, + "trusted": { + "message": "Uzticama" + }, "creatingAccountOn": { "message": "Tiek veidots konts" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Lietotāju pārvaldīšanai ir jābūt iespējotai arī ar konta atkopšanas pārvaldīšanas atļauju" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Apstiprināt pieprasījumu" }, + "deviceApproved": { + "message": "Ierīce apstiprināta" + }, + "deviceRemoved": { + "message": "Ierīce noņemta" + }, + "removeDevice": { + "message": "Noņemt ierīci" + }, + "removeDeviceConfirmation": { + "message": "Vai tiešām noņemt šo ierīci?" + }, "noDeviceRequests": { "message": "Nav ierīču pieprasījumu" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Noņemt dalībniekus" }, + "devices": { + "message": "Ierīces" + }, + "deviceListDescription": { + "message": "Kontā ir notikusi pieteikšanās katrā no zemāk uzskaitītajām ierīcēm. Ja kāda no tām nav atpazīstama, tā ir uzreiz jānoņem." + }, + "deviceListDescriptionTemp": { + "message": "Ar kontu ir veikta pieteikšanās katrā no zemāk uzskaitītajām ierīcēm." + }, "claimedDomains": { "message": "Pieteiktie domēni" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Apvienības abonements atsākts" + }, + "restartSubscription": { + "message": "Atsākt savu abonementu" + }, + "suspendedManagedOrgMessage": { + "message": "Jāsazinās ar $PROVIDER$, lai iegūtu palīdzību.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index c6f9aa8d853..a6eb0e474fd 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "ദയവായി തിരികെ പ്രവേശിക്കുക." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "ഈ ഉപയോക്താവ് അവരുടെ അക്കൗണ്ട് രണ്ട്-പ്രവേശനം ഉപയോഗിച്ച് സുരക്ഷിതമാക്കിയിരിക്കുന്നു." }, @@ -3765,6 +3780,15 @@ "device": { "message": "ഉപകരണം" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index af26fc4df94..da80546c9a5 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Pålogging startet" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Vennligst logg på igjen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vennligst logg inn på nytt. Dersom du bruker andre Bitwarden-applikasjoner logg av og på på dem også." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Denne brukeren bruker 2-trinnsinnlogging til å beskytte kontoen sin." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index e51fb3ced67..7ebca5938e0 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index cd72a94251c..b38e6f4c3c3 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Controleer je identiteit" }, + "whatIsADevice": { + "message": "Wat is een apparaat?" + }, + "aDeviceIs": { + "message": "Een apparaat is een unieke installatie van de Bitwarden-app waar je bent ingelogd. Het opnieuw installeren, verwijderen van app-gegevens of het wissen van uw cookies kan zorgen voor het meerdere keren weergeven van dat apparaat." + }, "logInInitiated": { "message": "Inloggen gestart" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Log opnieuw in." }, + "currentSession": { + "message": "Huidige sessie" + }, + "requestPending": { + "message": "Verzoek in behandeling" + }, "logBackInOthersToo": { "message": "Svp opnieuw inloggen. Als je andere Bitwarden-applicaties gebruikt, dan moet je daar ook uit- en inloggen." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Je hebt 1 uitnodiging over." + }, "userUsingTwoStep": { "message": "Het account van deze gebruiker is beschermd met tweestapsaanmelding." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Apparaat" }, + "loginStatus": { + "message": "Loginstatus" + }, + "firstLogin": { + "message": "Eerst inloggen" + }, + "trusted": { + "message": "Vertrouwd" + }, "creatingAccountOn": { "message": "Account maken bij" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gebruikers beheren moet je ook accountherstel beheren toekennen" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aanvraag goedkeuren" }, + "deviceApproved": { + "message": "Apparaat goedgekeurd" + }, + "deviceRemoved": { + "message": "Apparaat verwijderd" + }, + "removeDevice": { + "message": "Apparaat verwijderen" + }, + "removeDeviceConfirmation": { + "message": "Weet je zeker dat je dit apparaat wilt verwijderen?" + }, "noDeviceRequests": { "message": "Geen apparaatverzoeken" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Leden verwijderen" }, + "devices": { + "message": "Apparaten" + }, + "deviceListDescription": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld. Als je een apparaat niet herkent, verwijder het nu." + }, + "deviceListDescriptionTemp": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld." + }, "claimedDomains": { "message": "Geverifieerde domeinen" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisatieabonnement hervat" + }, + "restartSubscription": { + "message": "Abonnement hervatten" + }, + "suspendedManagedOrgMessage": { + "message": "Neem contact op met $PROVIDER$ voor hulp.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 0e3c134ba4d..cb6d070485d 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Eining" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 58e4de95317..d063fa818a7 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Zweryfikuj swoją tożsamość" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Logowanie rozpoczęte" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Zaloguj się ponownie." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Zaloguj się ponownie. Jeśli używasz innych aplikacji Bitwarden, wyloguj się i zaloguj ponownie również w nich." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ten użytkownik korzysta z logowania dwustopniowego, aby chronić swoje konto." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Urządzenie" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Tworzenie konta na" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Zarządzanie użytkownikami musi być również przyznane z uprawnieniem do zarządzania odzyskiwaniem kont" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Zatwierdź prośbę" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Brak urządzeń do zatwierdzenia" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 463b7f5e060..b911d5b273a 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifique sua identidade" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Login iniciado" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Por favor, reinicie a sessão." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, reinicie a sessão. Se estiver usando outros aplicativos do Bitwarden, encerre a sessão e reinicie também." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Este usuário está usando o login em duas etapas para proteger a sua conta." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Criando conta em" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gerenciar usuários também devem ser concedidos com a permissão de gerenciar a recuperação de contas" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprovar solicitação" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Nenhum pedido de dispositivo" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remover membro?" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 57a2eee7cf5..637c65f644d 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifique a sua identidade" }, + "whatIsADevice": { + "message": "O que é um dispositivo?" + }, + "aDeviceIs": { + "message": "Um dispositivo é uma instalação única da app Bitwarden onde o utilizador iniciou sessão. Reinstalar, limpar os dados da aplicação ou limpar os seus cookies pode fazer com que um dispositivo apareça várias vezes." + }, "logInInitiated": { "message": "A preparar o início de sessão" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Por favor, inicie sessão novamente." }, + "currentSession": { + "message": "Sessão atual" + }, + "requestPending": { + "message": "Pedido pendente" + }, "logBackInOthersToo": { "message": "Por favor, inicie sessão novamente. Se estiver a utilizar outras aplicações Bitwarden, termine a sessão e volte a iniciar sessão nessas também." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ainda tem 1 convite." + }, "userUsingTwoStep": { "message": "Este utilizador está a utilizar a verificação de dois passos para proteger a sua conta." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Estado do início de sessão" + }, + "firstLogin": { + "message": "Primeiro início de sessão" + }, + "trusted": { + "message": "Confiável" + }, "creatingAccountOn": { "message": "A criar conta em" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A permissão para gerir utilizadores deve também ser concedida com a permissão para gerir a recuperação da conta" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprovar pedido" }, + "deviceApproved": { + "message": "Dispositivo aprovado" + }, + "deviceRemoved": { + "message": "Dispositivo removido" + }, + "removeDevice": { + "message": "Remover dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Tem a certeza de que pretende remover este dispositivo?" + }, "noDeviceRequests": { "message": "Sem pedidos de dispositivos" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remover membros" }, + "devices": { + "message": "Dispositivos" + }, + "deviceListDescription": { + "message": "A sua conta foi iniciada em cada um dos dispositivos abaixo. Se não reconhecer um dispositivo, remova-o agora." + }, + "deviceListDescriptionTemp": { + "message": "Tem sessão iniciada em cada um dos dispositivos abaixo." + }, "claimedDomains": { "message": "Domínios reivindicados" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Subscrição da organização reiniciada" + }, + "restartSubscription": { + "message": "Reinicie a sua subscrição" + }, + "suspendedManagedOrgMessage": { + "message": "Contacte a $PROVIDER$ para obter assistência.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 18ff0b2158f..42d13688444 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Autentificare inițiată" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Vă rugăm să vă conectați din nou." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vă rugăm să vă reconectați. Dacă utilizați și alte aplicații Bitwarden, reconectați-vă la ele de asemenea." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Acest utilizator folosește conectarea în două etape pentru a-și proteja contul." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispozitiv" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 89209f2fa52..28386190d17 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Подтвердите вашу личность" }, + "whatIsADevice": { + "message": "Что такое устройство?" + }, + "aDeviceIs": { + "message": "Устройство - это уникальная установка приложения Bitwarden, в которой вы авторизовались. Переустановка, очистка данных приложения или очистка файлов cookie может привести к тому, что устройство будет отображаться несколько раз." + }, "logInInitiated": { "message": "Вход инициирован" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Пожалуйста, войдите снова." }, + "currentSession": { + "message": "Текущая сессия" + }, + "requestPending": { + "message": "Запрос в ожидании" + }, "logBackInOthersToo": { "message": "Пожалуйста, войдите снова. Если вы используете другие приложения Bitwarden, выполните на них выход и повторный вход." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "У вас осталось 1 приглашение." + }, "userUsingTwoStep": { "message": "Этот пользователь использует двухэтапную аутентификацию для защиты своего аккаунта." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Статус входа" + }, + "firstLogin": { + "message": "Первый вход" + }, + "trusted": { + "message": "Доверенный" + }, "creatingAccountOn": { "message": "Создание аккаунта" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для управления пользователями также необходимо предоставить разрешение на управление восстановлением аккаунта" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Одобрить запрос" }, + "deviceApproved": { + "message": "Устройство одобрено" + }, + "deviceRemoved": { + "message": "Устройство удалено" + }, + "removeDevice": { + "message": "Удалить устройство" + }, + "removeDeviceConfirmation": { + "message": "Вы уверены, что хотите удалить это устройство?" + }, "noDeviceRequests": { "message": "Нет запросов устройств" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Удалить участников" }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Ваш аккаунт был авторизован на каждом из перечисленных ниже устройств. Если устройство вам не знакомо, удалите его сейчас." + }, + "deviceListDescriptionTemp": { + "message": "Ваш аккаунт авторизован на перечисленных ниже устройствах." + }, "claimedDomains": { "message": "Зарегистрированные домены" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Подписка организации перезапущена" + }, + "restartSubscription": { + "message": "Перезапустить подписку" + }, + "suspendedManagedOrgMessage": { + "message": "Обратитесь за помощью к $PROVIDER$.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 0649f83f519..df5803c0fd9 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index dbffa25048c..d8adb9116ec 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Overte svoju totožnosť" }, + "whatIsADevice": { + "message": "Čo je zariadenie?" + }, + "aDeviceIs": { + "message": "Zariadenie je jednotlivá inštalácia aplikácie Bitwarden, do ktorej ste sa prihlásili. Preinštalovanie, vymazanie údajov aplikácie alebo vymazanie súborov cookie môže mať za následok, že sa zariadenie objaví viackrát." + }, "logInInitiated": { "message": "Iniciované prihlásenie" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Prosím, opäť sa prihláste." }, + "currentSession": { + "message": "Aktuálna relácia" + }, + "requestPending": { + "message": "Žiadosť čaká na spracovanie" + }, "logBackInOthersToo": { "message": "Prosím odhláste sa. Ak používate iné Bitwarden aplikácie, odhláste sa a opäť sa prihláste aj v nich." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ostáva vám 1 pozvánka." + }, "userUsingTwoStep": { "message": "Tento používateľ používa dvojstupňové overovanie aby si zabezpečil konto." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Zariadenie" }, + "loginStatus": { + "message": "Stav prihlásenia" + }, + "firstLogin": { + "message": "Prvé prihlásenie" + }, + "trusted": { + "message": "Dôveryhodné" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Schváliť žiadosť" }, + "deviceApproved": { + "message": "Zariadenie schválené" + }, + "deviceRemoved": { + "message": "Zariadenie odstránené" + }, + "removeDevice": { + "message": "Odstrániť zariadenie" + }, + "removeDeviceConfirmation": { + "message": "Ste si istí, že chcete odstrániť toto zariadenie?" + }, "noDeviceRequests": { "message": "Žiadne žiadosti pre zariadenia" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Odstrániť členov" }, + "devices": { + "message": "Zariadenia" + }, + "deviceListDescription": { + "message": "Vaše konto bolo prihlásené do každého z nižšie uvedených zariadení. Ak niektoré zariadenie nepoznáte, teraz ho odstráňte." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Privlastnené domény" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 9a9535aebaa..75ad87494f0 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Prijava se je začela" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Prosimo, ponovno se prijavite." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Prosimo, ponovno se prijavite. Če uporabljate druge Bitwarden aplikacije, se odjavite in ponovno prijavite tudi tam." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index e15d6d66653..0f3853331c2 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Потврдите идентитет" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Пријава је покренута" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Молимо да се поново пријавите." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Молимо вас да се поново пријавите. Ако користите друге Bitwarden апликације, одјавите се и вратите се и на њих." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Овај корисник користи пријаву у два корака за заштиту свог налога." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Уређај" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Креирај налог на" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Корисницима за управљање такође мора бити додељена дозвола за опоравак налога за управљање" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Одобри захтев" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Нема захтева уређаја" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Уклони чланове" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index b2cd3a877d4..06572d08fea 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 14d7bc4572c..f9737643631 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Inloggning påbörjad" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Vänligen logga in igen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vänligen logga in igen. Om du använder andra Bitwarden-applikationer, logga ut och in igen i dem också." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Denna användare använder tvåstegsverifiering för att skydda sitt konto." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Skapa konto på" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Inga enhetsförfrågningar" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index c731b9ff87e..5f5818581f6 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index ba53ebe7dd4..97c0d00dec2 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Kimliğinizi doğrulayın" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Giriş başlatıldı" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Lütfen yeniden giriş yapın." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Lütfen yeniden oturum açın. Diğer Bitwarden uygulamalarını kullanıyorsanız onlarda da oturumunuzu kapatıp yeniden açın." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 davetiyeniz kaldı." + }, "userUsingTwoStep": { "message": "Bu kullanıcı hesabını korumak için iki aşamalı giriş kullanıyor." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Hesap oluşturuluyor:" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "İsteği onayla" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Cihaz isteği yok" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Hesabınıza aşağıdaki cihazlardan giriş yapıldı." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index c17befc27ce..d8cd9270763 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Підтвердьте свою особу" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Ініційовано вхід" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Повторно виконайте вхід." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Будь ласка, повторно виконайте вхід. Якщо ви користуєтесь іншими програмами Bitwarden, також вийдіть із них, і знову увійдіть." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Цей користувач використовує двоетапну перевірку для захисту свого облікового запису." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Пристрій" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Створення облікового запису" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Разом із дозволом на керування відновленням облікового запису також необхідно надати дозвіл на керування користувачами" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Схвалити запит" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Немає запитів з пристрою" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Вилучити учасників" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Заявлені домени" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 9f4156014c6..ff9f789f6f4 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Xác minh danh tính của bạn" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Hãy đăng nhập lại." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vui lòng đăng nhập lại. Nếu bạn đang dùng những ứng dụng Bitwarden khác, vui lòng đăng xuất and đăng nhập lại những ứng dụng đó." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Thiết bị" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f293adcf62a..ae0b3744ec6 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -172,7 +172,7 @@ "message": "登录凭据" }, "personalDetails": { - "message": "个人信息" + "message": "个人详细信息" }, "identification": { "message": "身份" @@ -181,10 +181,10 @@ "message": "联系信息" }, "cardDetails": { - "message": "支付卡详情" + "message": "支付卡详细信息" }, "cardBrandDetails": { - "message": "$BRAND$ 详情", + "message": "$BRAND$ 详细信息", "placeholders": { "brand": { "content": "$1", @@ -684,7 +684,7 @@ "message": "项目" }, "itemDetails": { - "message": "项目详情" + "message": "项目详细信息" }, "itemName": { "message": "项目名称" @@ -958,13 +958,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "验证您的身份" }, + "whatIsADevice": { + "message": "什么是设备?" + }, + "aDeviceIs": { + "message": "设备是您登录过的 Bitwarden App 的独立安装。重新安装、清除 App 数据或清除 Cookie,可能会导致设备多次出现。" + }, "logInInitiated": { "message": "登录已发起" }, @@ -1707,7 +1713,7 @@ } }, "loggedOutWarning": { - "message": "接下来将会注销您当前的会话,要求您重新登录。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "emailChanged": { "message": "电子邮箱已保存" @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "请重新登录。" }, + "currentSession": { + "message": "当前会话" + }, + "requestPending": { + "message": "请求待处理" + }, "logBackInOthersToo": { "message": "请重新登录。如果您还在使用其他 Bitwarden 应用程序,也请注销并重新登陆。" }, @@ -1795,7 +1807,7 @@ "message": "您是否担心自己的账户在其他设备上登录过?请按照以下步骤取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" }, "deauthorizeSessionsWarning": { - "message": "接下来将会注销您当前的会话,并要求您重新登录。如果有设置两步登录,也需要重新认证。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作还将使您退出当前会话,并要求您重新登录。如果有设置两步登录,也需要重新验证。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "sessionsDeauthorized": { "message": "已取消所有会话授权" @@ -2141,7 +2153,7 @@ "message": "前往 bitwarden.com 吗?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。在 bitwarden.com 网站上了解更多信息。" + "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。访问 bitwarden.com 网站了解更多信息。" }, "twoStepAuthenticatorScanCodeV2": { "message": "用您的验证器 App 扫描下面的二维码或输入密钥。" @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "您还剩下 1 个邀请。" + }, "userUsingTwoStep": { "message": "此用户正在使用两步登录来保护他们的账户。" }, @@ -3765,6 +3780,15 @@ "device": { "message": "设备" }, + "loginStatus": { + "message": "登录状态" + }, + "firstLogin": { + "message": "首次登录" + }, + "trusted": { + "message": "信任" + }, "creatingAccountOn": { "message": "创建账户至" }, @@ -4819,7 +4843,7 @@ "message": "先决条件" }, "requireSsoPolicyReq": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "requireSsoPolicyReqError": { "message": "单一组织策略未启用。" @@ -5545,7 +5569,7 @@ "message": "对于具有主密码的现有账户,需要成员自行注册后,管理员才可以恢复他们的账户。对于新的成员,自动注册后将打开账户恢复功能。" }, "accountRecoverySingleOrgRequirementDesc": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "resetPasswordPolicyAutoEnroll": { "message": "自动注册" @@ -5655,6 +5679,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "授予「管理账户恢复」权限时,必须同时授予「管理用户」权限" }, @@ -5793,10 +5831,10 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问此密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "masterPasswordInvalidWarning": { - "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -6546,7 +6584,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6556,7 +6594,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "批准请求" }, + "deviceApproved": { + "message": "设备已批准" + }, + "deviceRemoved": { + "message": "设备已移除" + }, + "removeDevice": { + "message": "移除设备" + }, + "removeDeviceConfirmation": { + "message": "确定要移除此设备吗?" + }, "noDeviceRequests": { "message": "无设备请求" }, @@ -8975,7 +9025,7 @@ } }, "deleteProviderWarningDescription": { - "message": "删除 $ID$ 之前,您必须取消链接所有的客户。", + "message": "您必须取消链接所有的客户,然后才能删除 $ID$。", "placeholders": { "id": { "content": "$1", @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "移除成员" }, + "devices": { + "message": "设备" + }, + "deviceListDescription": { + "message": "您的账户在以下设备上登录过。如果您无法识别某个设备,请立即将其移除。" + }, + "deviceListDescriptionTemp": { + "message": "您的账户在以下设备上登录过。" + }, "claimedDomains": { "message": "已声明的域名" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "组织订阅已重启" + }, + "restartSubscription": { + "message": "重启您的订阅" + }, + "suspendedManagedOrgMessage": { + "message": "联系 $PROVIDER$ 获取协助。", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index ae60ea3cdb7..002d0d55e0f 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "核實你的身份" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "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." + }, "logInInitiated": { "message": "登入已發起" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "請重新登入。" }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "請重新登入。若您還在使用其他 Bitwarden 應用程式,也請登出後再重新登入。" }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "此使用者正在使用兩步驟登入保護帳戶。" }, @@ -3765,6 +3780,15 @@ "device": { "message": "裝置" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "建立帳號於" }, @@ -5655,6 +5679,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "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.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "管理使用者也必須被授予管理帳戶復原的權限" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "核准要求" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "沒有裝置要求" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } From 53618e8f8671009cd1907eeaa6cb7083fab337f1 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:52:18 +0000 Subject: [PATCH 147/270] Autosync the updated translations (#12824) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 6 +++++ apps/desktop/src/locales/ar/messages.json | 6 +++++ apps/desktop/src/locales/az/messages.json | 6 +++++ apps/desktop/src/locales/be/messages.json | 6 +++++ apps/desktop/src/locales/bg/messages.json | 6 +++++ apps/desktop/src/locales/bn/messages.json | 6 +++++ apps/desktop/src/locales/bs/messages.json | 6 +++++ apps/desktop/src/locales/ca/messages.json | 6 +++++ apps/desktop/src/locales/cs/messages.json | 6 +++++ apps/desktop/src/locales/cy/messages.json | 6 +++++ apps/desktop/src/locales/da/messages.json | 6 +++++ apps/desktop/src/locales/de/messages.json | 26 ++++++++++++-------- apps/desktop/src/locales/el/messages.json | 6 +++++ apps/desktop/src/locales/en_GB/messages.json | 6 +++++ apps/desktop/src/locales/en_IN/messages.json | 6 +++++ apps/desktop/src/locales/eo/messages.json | 6 +++++ apps/desktop/src/locales/es/messages.json | 6 +++++ apps/desktop/src/locales/et/messages.json | 6 +++++ apps/desktop/src/locales/eu/messages.json | 6 +++++ apps/desktop/src/locales/fa/messages.json | 6 +++++ apps/desktop/src/locales/fi/messages.json | 6 +++++ apps/desktop/src/locales/fil/messages.json | 6 +++++ apps/desktop/src/locales/fr/messages.json | 6 +++++ apps/desktop/src/locales/gl/messages.json | 6 +++++ apps/desktop/src/locales/he/messages.json | 6 +++++ apps/desktop/src/locales/hi/messages.json | 6 +++++ apps/desktop/src/locales/hr/messages.json | 6 +++++ apps/desktop/src/locales/hu/messages.json | 6 +++++ apps/desktop/src/locales/id/messages.json | 6 +++++ apps/desktop/src/locales/it/messages.json | 6 +++++ apps/desktop/src/locales/ja/messages.json | 6 +++++ apps/desktop/src/locales/ka/messages.json | 6 +++++ apps/desktop/src/locales/km/messages.json | 6 +++++ apps/desktop/src/locales/kn/messages.json | 6 +++++ apps/desktop/src/locales/ko/messages.json | 6 +++++ apps/desktop/src/locales/lt/messages.json | 6 +++++ apps/desktop/src/locales/lv/messages.json | 6 +++++ apps/desktop/src/locales/me/messages.json | 6 +++++ apps/desktop/src/locales/ml/messages.json | 6 +++++ apps/desktop/src/locales/mr/messages.json | 6 +++++ apps/desktop/src/locales/my/messages.json | 6 +++++ apps/desktop/src/locales/nb/messages.json | 6 +++++ apps/desktop/src/locales/ne/messages.json | 6 +++++ apps/desktop/src/locales/nl/messages.json | 6 +++++ apps/desktop/src/locales/nn/messages.json | 6 +++++ apps/desktop/src/locales/or/messages.json | 6 +++++ apps/desktop/src/locales/pl/messages.json | 6 +++++ apps/desktop/src/locales/pt_BR/messages.json | 6 +++++ apps/desktop/src/locales/pt_PT/messages.json | 26 ++++++++++++-------- apps/desktop/src/locales/ro/messages.json | 6 +++++ apps/desktop/src/locales/ru/messages.json | 6 +++++ apps/desktop/src/locales/si/messages.json | 6 +++++ apps/desktop/src/locales/sk/messages.json | 6 +++++ apps/desktop/src/locales/sl/messages.json | 6 +++++ apps/desktop/src/locales/sr/messages.json | 6 +++++ apps/desktop/src/locales/sv/messages.json | 6 +++++ apps/desktop/src/locales/te/messages.json | 6 +++++ apps/desktop/src/locales/th/messages.json | 6 +++++ apps/desktop/src/locales/tr/messages.json | 6 +++++ apps/desktop/src/locales/uk/messages.json | 6 +++++ apps/desktop/src/locales/vi/messages.json | 6 +++++ apps/desktop/src/locales/zh_CN/messages.json | 6 +++++ apps/desktop/src/locales/zh_TW/messages.json | 6 +++++ 63 files changed, 398 insertions(+), 20 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 9339d31aa9e..5ee3ba7e029 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Swak wagwoord geidentifiseer en gevind in 'n data lekkasie. Gebruik 'n sterk en unieke wagwoord om jou rekening te beskerm. Is jy seker dat jy hierdie wagwoord wil gebruik?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontroleer bekende data lekkasies vir hierdie wagwoord" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index bb21e0db35f..132021f7760 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "كلمة مرور ضعيفة محددة وموجودة في خرق البيانات. استخدم كلمة مرور قوية وفريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة المرور هذه؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "تحقق من خروقات البيانات المعروفة لكلمة المرور هذه" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 82fbee81114..9af089d1ef9 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zəif parol məlumat pozuntusunda aşkarlandı və tapıldı. Hesabınızı qorumaq üçün güclü və unikal bir parol istifadə edin. Bu parolu istifadə etmək istədiyinizə əminsiniz?" }, + "useThisPassword": { + "message": "Bu parolu istifadə et" + }, + "useThisUsername": { + "message": "Bu istifadəçi adını istifadə et" + }, "checkForBreaches": { "message": "Bu parol üçün bilinən məlumat pozuntularını yoxlayın" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 288ba2bede4..e778c59525f 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Вызначаны ненадзейны пароль, які знойдзены ва ўцечках даных. Выкарыстоўвайце надзейныя і ўнікальныя паролі для абароны свайго ўліковага запісу. Вы сапраўды хочаце выкарыстоўваць гэты пароль?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Праверыць у вядомых уцечках даных для гэтага пароля" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 3d730a67892..9b000177bbe 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Разпозната е слаба парола. Използвайте силна парола, за да защитете данните си. Наистина ли искате да използвате слаба парола?" }, + "useThisPassword": { + "message": "Използване на тази парола" + }, + "useThisUsername": { + "message": "Използване на това потребителско име" + }, "checkForBreaches": { "message": "Проверяване в известните случаи на изтекли данни за тази парола" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index e4eb247c59e..069c58d751d 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 9b91d3d7c43..f5052559de9 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 2367af72d5c..9d82ad59fad 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contrasenya feble identificada i trobada en una filtració de dades. Utilitzeu una contrasenya única i segura per protegir el vostre compte. Esteu segur que voleu utilitzar aquesta contrasenya?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Comproveu les filtracions de dades conegudes per a aquesta contrasenya" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 01d3380d27f..83f2840b724 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slabé heslo bylo nalezeno mezi odhalenými hesly. K zabezpečení Vašeho účtu používejte silné a jedinečné heslo. Opravdu chcete používat toto heslo?" }, + "useThisPassword": { + "message": "Použít toto heslo" + }, + "useThisUsername": { + "message": "Použít toto uživatelské jméno" + }, "checkForBreaches": { "message": "Zkontrolovat heslo, zda nebylo odhaleno" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 907f7498f2f..f4e0853a933 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 766f0cf5e71..ebe74818f47 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Svag adgangskode identificeret og fundet i datalæk. Brug en unik adgangskode til at beskytte din konto. Sikker på, at at denne adgangskode skal bruges?" }, + "useThisPassword": { + "message": "Anvend denne adgangskode" + }, + "useThisUsername": { + "message": "Anvend dette brugernavn" + }, "checkForBreaches": { "message": "Tjek kendte datalæk for denne adgangskode" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index d8e946bc3ee..133d98c3faa 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -250,17 +250,17 @@ "message": "Fehler" }, "decryptionError": { - "message": "Decryption error" + "message": "Entschlüsselungsfehler" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Kundensupport kontaktieren", "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": "um zusätzlichen Datenverlust zu vermeiden.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Schwaches Passwort erkannt und in einem Datendiebstahl gefunden. Verwende ein starkes und einzigartiges Passwort, um dein Konto zu schützen. Bist du sicher, dass du dieses Passwort verwenden möchtest?" }, + "useThisPassword": { + "message": "Dieses Passwort verwenden" + }, + "useThisUsername": { + "message": "Diesen Benutzernamen verwenden" + }, "checkForBreaches": { "message": "Bekannte Datendiebstähle auf dieses Passwort überprüfen" }, @@ -3377,19 +3383,19 @@ "message": "Es konnten keine freien Ports für die SSO-Anmeldung gefunden werden." }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." }, "biometricsStatusHelptextNotEnabledLocally": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", "placeholders": { "email": { "content": "$1", @@ -3398,7 +3404,7 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." }, "authorize": { "message": "Autorisieren" diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index b134c0d3db8..145f386c6b9 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Βρέθηκε και ταυτοποιήθηκε αδύναμος κωδικός σε μια διαρροή δεδομένων. Χρησιμοποιήστε ένα ισχυρό και μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό πρόσβασης;" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Ελέγξτε γνωστές διαρροές δεδομένων για αυτόν τον κωδικό" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 1f705b54329..acca06b8b4f 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index bdabdb2f376..122217dae9d 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 7cbd15b5965..a4621439f50 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 27f694ec189..1915002b6bd 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contraseña débil encontrada en una filtración de datos. Utilice una contraseña única para proteger su cuenta. ¿Está seguro de que desea utilizar una contraseña comprometida?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Comprobar filtración de datos conocidos para esta contraseña" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index ec73ac1e8b2..8396316416b 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Tuvastati nõrk ning andmelekkes lekkinud ülemparool. Kasuta konto paremaks turvamiseks tugevamat parooli. Oled kindel, et soovid nõrga parooliga jätkata?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Otsi seda parooli teadaolevatest andmeleketest" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 0147b474ab1..2daed855e52 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index a04c39d4ae0..b79dc2d90ee 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "کلمه عبور ضعیف شناسایی و در یک نقض داده پیدا شد. از یک کلمه عبور قوی و منحصر به فرد برای محافظت از حساب خود استفاده کنید. آیا مطمئنید که می‌خواهید از این کلمه عبور استفاده کنید؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "نقض اطلاعات شناخته شده برای این کلمه عبور را بررسی کنید" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 193553cac83..2d0a8cae996 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Havaittiin heikko ja tietovuodosta löytynyt salasana. Sinun tulisi suojata tilisi vahvalla ja ainutlaatuisella salasanalla. Haluatko varmasti käyttää tätä salasanaa?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Tarkasta esiintyykö salasanaa tunnetuissa tietovuodoissa" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 53b2ef9ffba..1f77b85e3c5 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mahinang password na nakilala at nakita sa data breach. Gamitin ang malakas at natatanging password upang makaproteksyon sa iyong account. Sigurado ka ba na gusto mong gamitin ang password na ito?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Tingnan ang kilalang breaches ng data para sa password na ito" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index f1bf4243874..f0ab36ba636 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mot de passe faible identifié et trouvé dans une brèche de données. Utilisez un mot de passe robuste et unique pour protéger votre compte. Êtes-vous sûr de vouloir utiliser ce mot de passe ?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Vérifier les brèches de données connues pour ce mot de passe" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 35d5cf8c03c..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 09d6e321a12..df61cefe73d 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 3263191cd31..723ea0f6992 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index b1def7c192c..109a1abff21 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slaba lozinka je nađena među ukradenima tijekom krađa podataka. Za zaštitu svog računa koristi jaku i jedinstvenu lozinku. Želiš li svejedno korisiti slabu, ukradenu lozinku?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Provjeri je li lozinka ukradena prilikom krađe podataka" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index fb94d34b387..3716e1e67a9 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Gyenge jelszó lett azonosítva és megtalálva egy adatvédelmi incidens során. A fók védelme érdekében használjunk erős és egyedi jelszót. Biztosan használni szeretnénk ezt a jelszót?" }, + "useThisPassword": { + "message": "Jelszó használata" + }, + "useThisUsername": { + "message": "Felhasználónév használata" + }, "checkForBreaches": { "message": "Az ehhez a jelszóhoz tartozó ismert adatvédelmi incidensek ellenőrzése" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 989177b1b43..d4bcded0ee8 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Periksa pelanggaran data yang diketahui untuk kata sandi ini" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 13e4c692f69..c85127fce20 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Password debole e trovata in una violazione dei dati. Usa una password forte e unica per proteggere il tuo account. Sei sicuro di voler usare questa password?" }, + "useThisPassword": { + "message": "Usa questa parola d'accesso" + }, + "useThisUsername": { + "message": "Usa questo nome utente" + }, "checkForBreaches": { "message": "Controlla se la tua password è presente in una violazione dei dati" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 364de39788b..9ce8f6a3ea7 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "入力されたパスワードは脆弱かつすでに流出済みです。アカウントを守るためより強力で一意なパスワードを使用してください。本当にこの脆弱なパスワードを使用しますか?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "このパスワードの既知のデータ流出を確認" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index fef2d56a7c2..bf434a6c29f 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 35d5cf8c03c..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 9ce484b3810..f3401477f4a 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 19a13ef3e81..1aafdc4bc6d 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index c1fafc59f12..11c70dd4197 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Silpnas slaptažodis nustatytas ir rastas per duomenų pažeidimą. Norėdami apsaugoti paskyrą, naudokite stiprų ir unikalų slaptažodį. Ar tikrai norite naudoti šį slaptažodį?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Patikrinti žinomus šio slaptažodžio duomenų pažeidimus" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index f2317e1d0bb..68495a25137 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Noteikta vāja parole, un tā ir atrasta datu noplūdē. Jāizmanto spēcīga un neatkārtojama parole, lai aizsargātu savu kontu. Vai tiešām izmantot šo paroli?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Meklēt šo paroli zināmās datu noplūdēs" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 50a6ee8df58..ac9994c3134 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index e1516b99b23..15d4de334eb 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 35d5cf8c03c..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index ce5a50e1f70..ca399731495 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 00970fb0fc6..0ebecf3be8f 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 6a5ae864fc1..624547d2121 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 729a093927e..6f3670afa3d 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zwak wachtwoord geïdentificeerd en gevonden in een datalek. Gebruik een sterk en uniek wachtwoord om je account te beschermen. Weet je zeker dat je dit wachtwoord wilt gebruiken?" }, + "useThisPassword": { + "message": "Dit wachtwoord gebruiken" + }, + "useThisUsername": { + "message": "Deze gebruikersnaam gebruiken" + }, "checkForBreaches": { "message": "Bekende datalekken voor dit wachtwoord controleren" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 7e18c7b77d2..df2f70ae839 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 903fe83af70..ea55a1c6029 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 59cf3a96ab9..d7b950100f5 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Słabe hasło ujawnione w wyniku naruszenia ochrony danych. Użyj silnego i unikalnego hasła, aby chronić swoje konto. Czy na pewno chcesz użyć tego hasła?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Sprawdź znane naruszenia ochrony danych tego hasła" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 0e35de82e7c..312fe4e8e89 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Verificar vazamentos de dados conhecidos para esta senha" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index a63fbca2ebc..d88862a7980 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -250,17 +250,17 @@ "message": "Erro" }, "decryptionError": { - "message": "Decryption error" + "message": "Erro de desencriptação" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Contacte o serviço de apoio ao cliente", "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": "para evitar perdas adicionais de dados.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?" }, + "useThisPassword": { + "message": "Utilizar esta palavra-passe" + }, + "useThisUsername": { + "message": "Utilizar este nome de utilizador" + }, "checkForBreaches": { "message": "Verificar violações de dados conhecidas para esta palavra-passe" }, @@ -3377,19 +3383,19 @@ "message": "Não foi possível encontrar portas livres para o início de sessão sso." }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "O desbloqueio biométrico está atualmente indisponível." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." }, "biometricsStatusHelptextNotEnabledLocally": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", "placeholders": { "email": { "content": "$1", @@ -3398,7 +3404,7 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." }, "authorize": { "message": "Autorizar" diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 939c3295898..d4ca5e21a5e 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index d8aa6556a0d..46e04b77704 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Обнаружен слабый пароль, найденный в утечке данных. Используйте надежный и уникальный пароль для защиты вашего аккаунта. Вы уверены, что хотите использовать этот пароль?" }, + "useThisPassword": { + "message": "Использовать этот пароль" + }, + "useThisUsername": { + "message": "Использовать это имя пользователя" + }, "checkForBreaches": { "message": "Проверять известные случаи утечки данных для этого пароля" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 210ccc7383b..5372bf59637 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index bfcfb56f984..07851ddc47e 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Nájdené slabé heslo v uniknuných údajoch. Na ochranu svojho účtu používajte silné a jedinečné heslo. Naozaj chcete používať toto heslo?" }, + "useThisPassword": { + "message": "Použiť toto heslo" + }, + "useThisUsername": { + "message": "Použiť toto používateľské meno" + }, "checkForBreaches": { "message": "Skontrolovať známe úniky údajov pre toto heslo" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 0270b7ba3f1..c4fae11c815 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index d8544d8285a..03cc5b8c265 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Идентификована је слаба лозинка и пронађена у упаду података. Користите јаку и јединствену лозинку да заштитите свој налог. Да ли сте сигурни да желите да користите ову лозинку?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Проверите познате упада података за ову лозинку" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 023b74b12b4..920981908fa 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Lösenordet är svagt och avslöjades vid ett dataintrång. Använd ett starkt och unikt lösenord för att skydda ditt konto. Är det säkert att du vill använda detta lösenord?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontrollera kända dataintrång för detta lösenord" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 35d5cf8c03c..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index b5cc86b1e54..7f7e373132c 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 5f11450a207..2fbd751ddb8 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Hem zayıf hem de veri ihlalinde yer alan bir tespit edildi. Hesabınızı korumak için güçlü bir parola seçin ve o parolayı başka yerlerde kullanmayın. Bu parolayı kullanmak istediğinizden emin misiniz?" }, + "useThisPassword": { + "message": "Bu parolayı kullan" + }, + "useThisUsername": { + "message": "Bu kullanıcı adını kullan" + }, "checkForBreaches": { "message": "Bilinen veri ihlallerinde bu parolayı kontrol et" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index d4126a6b647..db0259adc05 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Виявлено слабкий пароль, який знайдено у витоку даних. Використовуйте надійний та унікальний пароль для захисту свого облікового запису. Ви дійсно хочете використати цей пароль?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Перевірити відомі витоки даних для цього пароля" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index fcd113f3071..07dcdfd373c 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mật khẩu yếu này đã bị rò rỉ trong một vụ tấn công dữ liệu. Dùng mật khẩu mới và an toàn để bảo vệ tài khoản bạn. Bạn có chắc muốn sử dụng mật khẩu đã bị rò rỉ?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kiểm tra mật khẩu có lộ trong các vụ rò rỉ dữ liệu hay không" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 0285842f04e..c9046b34a55 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护你的账户。确定要使用这个密码吗?" }, + "useThisPassword": { + "message": "使用此密码" + }, + "useThisUsername": { + "message": "使用此用户名" + }, "checkForBreaches": { "message": "检查已知的数据泄露是否包含此密码" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index b5ca02d6c8b..a9206a50df6 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "密碼強度不足,且該密碼已洩露。請使用一個強度足夠和獨特的密碼來保護您的帳戶。您確定要使用這個密碼嗎?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "檢查外洩的密碼資料庫中是否包含此密碼" }, From fe9b0976ee85908f3c0f7da648b78b60dc363e20 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:17:30 +0100 Subject: [PATCH 148/270] Fix the page loading issue (#12828) --- .../app/billing/organizations/change-plan-dialog.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 d7ac442c40c..bc5c7da8db9 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 @@ -311,7 +311,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { setInitialPlanSelection() { this.focusedIndex = this.selectableProducts.length - 1; - this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + if (!this.isSubscriptionCanceled) { + this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + } } getPlanByType(productTier: ProductTierType) { From 22f4822efc4b1b89b2ecfc492043327000374b89 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:44:42 +0100 Subject: [PATCH 149/270] [PM-16470] Remove v1 autofill owned refresh code (#12579) * Remove v1 autofill owned settings * Remove Fido2 v1 components * Remove conditional to enable AutofillOnPageLoadOrgPolicy --------- Co-authored-by: Daniel James Smith --- .../fido2/fido2-cipher-row-v1.component.html | 36 -- .../fido2/fido2-cipher-row-v1.component.ts | 41 -- .../fido2-use-browser-link-v1.component.html | 52 -- .../fido2-use-browser-link-v1.component.ts | 115 ----- .../popup/fido2/fido2-v1.component.html | 142 ------ .../popup/fido2/fido2-v1.component.ts | 445 ------------------ .../popup/settings/autofill-v1.component.html | 270 ----------- .../popup/settings/autofill-v1.component.ts | 344 -------------- .../excluded-domains-v1.component.html | 91 ---- .../settings/excluded-domains-v1.component.ts | 143 ------ .../settings/notifications-v1.component.html | 89 ---- .../settings/notifications-v1.component.ts | 53 --- .../src/background/runtime.background.ts | 8 +- apps/browser/src/popup/app-routing.module.ts | 25 +- apps/browser/src/popup/app.module.ts | 20 - 15 files changed, 14 insertions(+), 1860 deletions(-) delete mode 100644 apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html delete mode 100644 apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts delete mode 100644 apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html delete mode 100644 apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts delete mode 100644 apps/browser/src/autofill/popup/fido2/fido2-v1.component.html delete mode 100644 apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts delete mode 100644 apps/browser/src/autofill/popup/settings/autofill-v1.component.html delete mode 100644 apps/browser/src/autofill/popup/settings/autofill-v1.component.ts delete mode 100644 apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html delete mode 100644 apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts delete mode 100644 apps/browser/src/autofill/popup/settings/notifications-v1.component.html delete mode 100644 apps/browser/src/autofill/popup/settings/notifications-v1.component.ts diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html deleted file mode 100644 index 852fd4a0e81..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html +++ /dev/null @@ -1,36 +0,0 @@ -
    -
    - -
    -
    diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts deleted file mode 100644 index 2101979fe36..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from "@angular/core"; - -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; - -@Component({ - selector: "app-fido2-cipher-row-v1", - templateUrl: "fido2-cipher-row-v1.component.html", - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class Fido2CipherRowV1Component { - @Output() onSelected = new EventEmitter(); - @Input() cipher: CipherView; - @Input() last: boolean; - @Input() title: string; - @Input() isSearching: boolean; - @Input() isSelected: boolean; - - protected selectCipher(c: CipherView) { - this.onSelected.emit(c); - } - - /** - * Returns a subname for the cipher. - * If this has a FIDO2 credential, and the cipher.name is different from the FIDO2 credential's rpId, return the rpId. - * @param c Cipher - * @returns - */ - protected getSubName(c: CipherView): string | null { - const fido2Credentials = c.login?.fido2Credentials; - - if (!fido2Credentials || fido2Credentials.length === 0) { - return null; - } - - const [fido2Credential] = fido2Credentials; - - return c.name !== fido2Credential.rpId ? fido2Credential.rpId : null; - } -} diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html deleted file mode 100644 index 9f6c0aca50d..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - -
    - -
    -
    - -
    -
    diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts deleted file mode 100644 index b7a1bc195ad..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts +++ /dev/null @@ -1,115 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { ConnectedPosition } from "@angular/cdk/overlay"; -import { Component } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -import { fido2PopoutSessionData$ } from "../../../vault/popup/utils/fido2-popout-session-data"; -import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-fido2-user-interface.service"; - -@Component({ - selector: "app-fido2-use-browser-link-v1", - templateUrl: "fido2-use-browser-link-v1.component.html", - animations: [ - trigger("transformPanel", [ - state( - "void", - style({ - opacity: 0, - }), - ), - transition( - "void => open", - animate( - "100ms linear", - style({ - opacity: 1, - }), - ), - ), - transition("* => void", animate("100ms linear", style({ opacity: 0 }))), - ]), - ], -}) -export class Fido2UseBrowserLinkV1Component { - showOverlay = false; - isOpen = false; - overlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "bottom", - overlayX: "start", - overlayY: "top", - offsetY: 5, - }, - ]; - - protected fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - private domainSettingsService: DomainSettingsService, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - ) {} - - toggle() { - this.isOpen = !this.isOpen; - } - - close() { - this.isOpen = false; - } - - /** - * Aborts the current FIDO2 session and fallsback to the browser. - * @param excludeDomain - Identifies if the domain should be excluded from future FIDO2 prompts. - */ - protected async abort(excludeDomain = true) { - this.close(); - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - - if (!excludeDomain) { - this.abortSession(sessionData.sessionId); - return; - } - // Show overlay to prevent the user from interacting with the page. - this.showOverlay = true; - await this.handleDomainExclusion(sessionData.senderUrl); - // Give the user a chance to see the toast before closing the popout. - await Utils.delay(2000); - this.abortSession(sessionData.sessionId); - } - - /** - * Excludes the domain from future FIDO2 prompts. - * @param uri - The domain uri to exclude from future FIDO2 prompts. - */ - private async handleDomainExclusion(uri: string) { - const existingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); - - const validDomain = Utils.getHostname(uri); - const savedDomains: NeverDomains = { - ...existingDomains, - }; - savedDomains[validDomain] = null; - - await this.domainSettingsService.setNeverDomains(savedDomains); - - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("domainAddedToExcludedDomains", validDomain), - ); - } - - private abortSession(sessionId: string) { - BrowserFido2UserInterfaceSession.abortPopout(sessionId, true); - } -} diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html deleted file mode 100644 index 8a052fbc5b7..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html +++ /dev/null @@ -1,142 +0,0 @@ - -
    -
    -
    - - - - - - -
    - - -
    - -
    -
    -
    - - - -
    -

    - {{ subtitleText | i18n }} -

    - - -
    -
    - -
    -
    - -
    - -
    -
    - - -
    - -
    -
    -
    -
    - -
    -

    {{ "passkeyAlreadyExists" | i18n }}

    -
    -
    - -
    -
    - -
    -
    - -
    -

    {{ "noPasskeysFoundForThisApplication" | i18n }}

    -
    - -
    -
    - - -
    -
    diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts deleted file mode 100644 index 8dc603cfe29..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts +++ /dev/null @@ -1,445 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { - BehaviorSubject, - combineLatest, - concatMap, - filter, - firstValueFrom, - map, - Observable, - Subject, - take, - takeUntil, -} from "rxjs"; - -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; -import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; -import { DialogService } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { ZonedMessageListenerService } from "../../../platform/browser/zoned-message-listener.service"; -import { VaultPopoutType } from "../../../vault/popup/utils/vault-popout-window"; -import { Fido2UserVerificationService } from "../../../vault/services/fido2-user-verification.service"; -import { - BrowserFido2Message, - BrowserFido2UserInterfaceSession, - BrowserFido2MessageTypes, -} from "../../fido2/services/browser-fido2-user-interface.service"; - -interface ViewData { - message: BrowserFido2Message; - fallbackSupported: boolean; -} - -@Component({ - selector: "app-fido2-v1", - templateUrl: "fido2-v1.component.html", - styleUrls: [], -}) -export class Fido2V1Component implements OnInit, OnDestroy { - private destroy$ = new Subject(); - private hasSearched = false; - - protected cipher: CipherView; - protected searchTypeSearch = false; - protected searchPending = false; - protected searchText: string; - protected url: string; - protected hostname: string; - protected data$: Observable; - protected sessionId?: string; - protected senderTabId?: string; - protected ciphers?: CipherView[] = []; - protected displayedCiphers?: CipherView[] = []; - protected loading = false; - protected subtitleText: string; - protected credentialText: string; - protected BrowserFido2MessageTypes = BrowserFido2MessageTypes; - - private message$ = new BehaviorSubject(null); - - constructor( - private router: Router, - private activatedRoute: ActivatedRoute, - private cipherService: CipherService, - private platformUtilsService: PlatformUtilsService, - private domainSettingsService: DomainSettingsService, - private searchService: SearchService, - private logService: LogService, - private dialogService: DialogService, - private browserMessagingApi: ZonedMessageListenerService, - private passwordRepromptService: PasswordRepromptService, - private fido2UserVerificationService: Fido2UserVerificationService, - private accountService: AccountService, - ) {} - - ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - - const queryParams$ = this.activatedRoute.queryParamMap.pipe( - take(1), - map((queryParamMap) => ({ - sessionId: queryParamMap.get("sessionId"), - senderTabId: queryParamMap.get("senderTabId"), - senderUrl: queryParamMap.get("senderUrl"), - })), - ); - - combineLatest([ - queryParams$, - this.browserMessagingApi.messageListener$() as Observable, - ]) - .pipe( - concatMap(async ([queryParams, message]) => { - this.sessionId = queryParams.sessionId; - this.senderTabId = queryParams.senderTabId; - this.url = queryParams.senderUrl; - // For a 'NewSessionCreatedRequest', abort if it doesn't belong to the current session. - if ( - message.type === BrowserFido2MessageTypes.NewSessionCreatedRequest && - message.sessionId !== queryParams.sessionId - ) { - this.abort(false); - return; - } - - // Ignore messages that don't belong to the current session. - if (message.sessionId !== queryParams.sessionId) { - return; - } - - if (message.type === BrowserFido2MessageTypes.AbortRequest) { - this.abort(false); - return; - } - - return message; - }), - filter((message) => !!message), - takeUntil(this.destroy$), - ) - .subscribe((message) => { - this.message$.next(message); - }); - - this.data$ = this.message$.pipe( - filter((message) => message != undefined), - concatMap(async (message) => { - switch (message.type) { - case BrowserFido2MessageTypes.ConfirmNewCredentialRequest: { - const equivalentDomains = await firstValueFrom( - this.domainSettingsService.getUrlEquivalentDomains(this.url), - ); - - this.ciphers = (await this.cipherService.getAllDecrypted()).filter( - (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, - ); - this.displayedCiphers = this.ciphers.filter( - (cipher) => - cipher.login.matchesUri(this.url, equivalentDomains) && - this.hasNoOtherPasskeys(cipher, message.userHandle), - ); - - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } - break; - } - - case BrowserFido2MessageTypes.PickCredentialRequest: { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - this.ciphers = await Promise.all( - message.cipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); - }), - ); - this.displayedCiphers = [...this.ciphers]; - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } - break; - } - - case BrowserFido2MessageTypes.InformExcludedCredentialRequest: { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - this.ciphers = await Promise.all( - message.existingCipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); - }), - ); - this.displayedCiphers = [...this.ciphers]; - - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } - break; - } - } - - this.subtitleText = - this.displayedCiphers.length > 0 - ? this.getCredentialSubTitleText(message.type) - : "noMatchingPasskeyLogin"; - - this.credentialText = this.getCredentialButtonText(message.type); - return { - message, - fallbackSupported: "fallbackSupported" in message && message.fallbackSupported, - }; - }), - takeUntil(this.destroy$), - ); - - queryParams$.pipe(takeUntil(this.destroy$)).subscribe((queryParams) => { - this.send({ - sessionId: queryParams.sessionId, - type: BrowserFido2MessageTypes.ConnectResponse, - }); - }); - } - - protected async submit() { - const data = this.message$.value; - if (data?.type === BrowserFido2MessageTypes.PickCredentialRequest) { - // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); - - this.send({ - sessionId: this.sessionId, - cipherId: this.cipher.id, - type: BrowserFido2MessageTypes.PickCredentialResponse, - userVerified, - }); - } else if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { - if (this.cipher.login.hasFido2Credentials) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "overwritePasskey" }, - content: { key: "overwritePasskeyAlert" }, - type: "info", - }); - - if (!confirmed) { - return false; - } - } - - // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); - - this.send({ - sessionId: this.sessionId, - cipherId: this.cipher.id, - type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, - userVerified, - }); - } - - this.loading = true; - } - - protected async saveNewLogin() { - const data = this.message$.value; - if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { - const name = data.credentialName || data.rpId; - // TODO: Revert to check for user verification once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - await this.createNewCipher(name, data.userName); - - // We are bypassing user verification pending approval. - this.send({ - sessionId: this.sessionId, - cipherId: this.cipher?.id, - type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, - userVerified: data.userVerification, - }); - } - - this.loading = true; - } - - getCredentialSubTitleText(messageType: string): string { - return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest - ? "chooseCipherForPasskeySave" - : "logInWithPasskeyQuestion"; - } - - getCredentialButtonText(messageType: string): string { - return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest - ? "savePasskey" - : "confirm"; - } - - selectedPasskey(item: CipherView) { - this.cipher = item; - } - - viewPasskey() { - // 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(["/view-cipher"], { - queryParams: { - cipherId: this.cipher.id, - uilocation: "popout", - senderTabId: this.senderTabId, - sessionId: this.sessionId, - singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, - }, - }); - } - - addCipher() { - const data = this.message$.value; - - if (data?.type !== BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { - return; - } - - // 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(["/add-cipher"], { - queryParams: { - name: data.credentialName || data.rpId, - uri: this.url, - type: CipherType.Login.toString(), - uilocation: "popout", - username: data.userName, - senderTabId: this.senderTabId, - sessionId: this.sessionId, - userVerification: data.userVerification, - singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, - }, - }); - } - - protected async search() { - this.hasSearched = await this.searchService.isSearchable(this.searchText); - this.searchPending = true; - if (this.hasSearched) { - this.displayedCiphers = await this.searchService.searchCiphers( - this.searchText, - null, - this.ciphers, - ); - } else { - const equivalentDomains = await firstValueFrom( - this.domainSettingsService.getUrlEquivalentDomains(this.url), - ); - this.displayedCiphers = this.ciphers.filter((cipher) => - cipher.login.matchesUri(this.url, equivalentDomains), - ); - } - this.searchPending = false; - this.selectedPasskey(this.displayedCiphers[0]); - } - - abort(fallback: boolean) { - this.unload(fallback); - window.close(); - } - - unload(fallback = false) { - this.send({ - sessionId: this.sessionId, - type: BrowserFido2MessageTypes.AbortResponse, - fallbackRequested: fallback, - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - private buildCipher(name: string, username: string) { - this.cipher = new CipherView(); - this.cipher.name = name; - - this.cipher.type = CipherType.Login; - this.cipher.login = new LoginView(); - this.cipher.login.username = username; - this.cipher.login.uris = [new LoginUriView()]; - this.cipher.login.uris[0].uri = this.url; - this.cipher.card = new CardView(); - this.cipher.identity = new IdentityView(); - this.cipher.secureNote = new SecureNoteView(); - this.cipher.secureNote.type = SecureNoteType.Generic; - this.cipher.reprompt = CipherRepromptType.None; - } - - private async createNewCipher(name: string, username: string) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - this.buildCipher(name, username); - const cipher = await this.cipherService.encrypt(this.cipher, activeUserId); - try { - await this.cipherService.createWithServer(cipher); - this.cipher.id = cipher.id; - } catch (e) { - this.logService.error(e); - } - } - - // TODO: Remove and use fido2 user verification service once user verification for passkeys is approved for production. - private async handleUserVerification( - userVerificationRequested: boolean, - cipher: CipherView, - ): Promise { - const masterPasswordRepromptRequired = cipher && cipher.reprompt !== 0; - - if (masterPasswordRepromptRequired) { - return await this.passwordRepromptService.showPasswordPrompt(); - } - - return userVerificationRequested; - } - - private send(msg: BrowserFido2Message) { - BrowserFido2UserInterfaceSession.sendMessage({ - sessionId: this.sessionId, - ...msg, - }); - } - - /** - * This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle - * @param userHandle - */ - private hasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean { - if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) { - return true; - } - - return cipher.login.fido2Credentials.some((passkey) => passkey.userHandle === userHandle); - } -} diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html deleted file mode 100644 index 1c16ee1fe12..00000000000 --- a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html +++ /dev/null @@ -1,270 +0,0 @@ -
    -
    - -
    -

    - {{ "autofill" | i18n }} -

    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - - -
    - -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - -
    -
    -

    {{ "additionalOptions" | i18n }}

    -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - -
    -
    -
    - -
    -
    -
    diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts b/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts deleted file mode 100644 index 9f015d990e9..00000000000 --- a/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts +++ /dev/null @@ -1,344 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { - InlineMenuVisibilitySetting, - ClearClipboardDelaySetting, -} from "@bitwarden/common/autofill/types"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { - UriMatchStrategy, - UriMatchStrategySetting, -} from "@bitwarden/common/models/domain/domain-service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { DialogService } from "@bitwarden/components"; - -import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "app-autofill-v1", - templateUrl: "autofill-v1.component.html", -}) -export class AutofillV1Component implements OnInit { - protected canOverrideBrowserAutofillSetting = false; - protected defaultBrowserAutofillDisabled = false; - protected autoFillOverlayVisibility: InlineMenuVisibilitySetting; - protected autoFillOverlayVisibilityOptions: any[]; - protected disablePasswordManagerLink: string; - protected inlineMenuPositioningImprovementsEnabled: boolean = false; - protected blockBrowserInjectionsByDomainEnabled: boolean = false; - protected showInlineMenuIdentities: boolean = true; - protected showInlineMenuCards: boolean = true; - inlineMenuIsEnabled: boolean = false; - enableAutoFillOnPageLoad = false; - autoFillOnPageLoadDefault = false; - autoFillOnPageLoadOptions: any[]; - enableContextMenuItem = false; - enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true? - clearClipboard: ClearClipboardDelaySetting; - clearClipboardOptions: any[]; - defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain; - uriMatchOptions: any[]; - showCardsCurrentTab = false; - showIdentitiesCurrentTab = false; - autofillKeyboardHelperText: string; - accountSwitcherEnabled = false; - - constructor( - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private domainSettingsService: DomainSettingsService, - private configService: ConfigService, - private dialogService: DialogService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - private messagingService: MessagingService, - private vaultSettingsService: VaultSettingsService, - ) { - this.autoFillOverlayVisibilityOptions = [ - { - name: i18nService.t("autofillOverlayVisibilityOff"), - value: AutofillOverlayVisibility.Off, - }, - { - name: i18nService.t("autofillOverlayVisibilityOnFieldFocus"), - value: AutofillOverlayVisibility.OnFieldFocus, - }, - { - name: i18nService.t("autofillOverlayVisibilityOnButtonClick"), - value: AutofillOverlayVisibility.OnButtonClick, - }, - ]; - this.autoFillOnPageLoadOptions = [ - { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, - { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, - ]; - this.clearClipboardOptions = [ - { name: i18nService.t("never"), value: null }, - { name: i18nService.t("tenSeconds"), value: 10 }, - { name: i18nService.t("twentySeconds"), value: 20 }, - { name: i18nService.t("thirtySeconds"), value: 30 }, - { name: i18nService.t("oneMinute"), value: 60 }, - { name: i18nService.t("twoMinutes"), value: 120 }, - { name: i18nService.t("fiveMinutes"), value: 300 }, - ]; - this.uriMatchOptions = [ - { name: i18nService.t("baseDomainOptionRecommended"), value: UriMatchStrategy.Domain }, - { name: i18nService.t("host"), value: UriMatchStrategy.Host }, - { name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith }, - { name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression }, - { name: i18nService.t("exact"), value: UriMatchStrategy.Exact }, - { name: i18nService.t("never"), value: UriMatchStrategy.Never }, - ]; - - this.accountSwitcherEnabled = enableAccountSwitching(); - this.disablePasswordManagerLink = this.getDisablePasswordManagerLink(); - } - - async ngOnInit() { - this.canOverrideBrowserAutofillSetting = - this.platformUtilsService.isChrome() || - this.platformUtilsService.isEdge() || - this.platformUtilsService.isOpera() || - this.platformUtilsService.isVivaldi(); - - this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden(); - - this.autoFillOverlayVisibility = await firstValueFrom( - this.autofillSettingsService.inlineMenuVisibility$, - ); - - this.inlineMenuPositioningImprovementsEnabled = await this.configService.getFeatureFlag( - FeatureFlag.InlineMenuPositioningImprovements, - ); - - this.blockBrowserInjectionsByDomainEnabled = await this.configService.getFeatureFlag( - FeatureFlag.BlockBrowserInjectionsByDomain, - ); - - this.inlineMenuIsEnabled = this.isInlineMenuEnabled(); - - this.showInlineMenuIdentities = - this.inlineMenuPositioningImprovementsEnabled && - (await firstValueFrom(this.autofillSettingsService.showInlineMenuIdentities$)); - - this.showInlineMenuCards = - this.inlineMenuPositioningImprovementsEnabled && - (await firstValueFrom(this.autofillSettingsService.showInlineMenuCards$)); - - this.enableAutoFillOnPageLoad = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoad$, - ); - - this.autoFillOnPageLoadDefault = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoadDefault$, - ); - - this.enableContextMenuItem = await firstValueFrom( - this.autofillSettingsService.enableContextMenu$, - ); - - this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$); - - this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$); - - const defaultUriMatch = await firstValueFrom( - this.domainSettingsService.defaultUriMatchStrategy$, - ); - this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch; - - const command = await this.platformUtilsService.getAutofillKeyboardShortcut(); - await this.setAutofillKeyboardHelperText(command); - - this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$); - - this.showIdentitiesCurrentTab = await firstValueFrom( - this.vaultSettingsService.showIdentitiesCurrentTab$, - ); - } - - isInlineMenuEnabled() { - return ( - this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnFieldFocus || - this.autoFillOverlayVisibility === AutofillOverlayVisibility.OnButtonClick - ); - } - - async updateAutoFillOverlayVisibility() { - await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility); - await this.requestPrivacyPermission(); - - this.inlineMenuIsEnabled = this.isInlineMenuEnabled(); - } - - async updateAutoFillOnPageLoad() { - await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad); - } - - async updateAutoFillOnPageLoadDefault() { - await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault); - } - - async saveDefaultUriMatch() { - await this.domainSettingsService.setDefaultUriMatchStrategy(this.defaultUriMatch); - } - - private async setAutofillKeyboardHelperText(command: string) { - if (command) { - this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutText", command); - } else { - this.autofillKeyboardHelperText = this.i18nService.t("autofillLoginShortcutNotSet"); - } - } - - async commandSettings() { - if (this.platformUtilsService.isChrome()) { - // 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 - BrowserApi.createNewTab("chrome://extensions/shortcuts"); - } else if (this.platformUtilsService.isOpera()) { - // 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 - BrowserApi.createNewTab("opera://extensions/shortcuts"); - } else if (this.platformUtilsService.isEdge()) { - // 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 - BrowserApi.createNewTab("edge://extensions/shortcuts"); - } else if (this.platformUtilsService.isVivaldi()) { - // 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 - BrowserApi.createNewTab("vivaldi://extensions/shortcuts"); - } 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 - BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts"); - } - } - - private getDisablePasswordManagerLink(): string { - if (this.platformUtilsService.isChrome()) { - return "chrome://settings/autofill"; - } - if (this.platformUtilsService.isOpera()) { - return "opera://settings/autofill"; - } - if (this.platformUtilsService.isEdge()) { - return "edge://settings/passwords"; - } - if (this.platformUtilsService.isVivaldi()) { - return "vivaldi://settings/autofill"; - } - - return "https://bitwarden.com/help/disable-browser-autofill/"; - } - - protected openDisablePasswordManagerLink(event: Event) { - event.preventDefault(); - // 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 - BrowserApi.createNewTab(this.disablePasswordManagerLink); - } - - async requestPrivacyPermission() { - if ( - this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off || - !this.canOverrideBrowserAutofillSetting || - (await this.browserAutofillSettingCurrentlyOverridden()) - ) { - return; - } - - await this.dialogService.openSimpleDialog({ - title: { key: "overrideDefaultBrowserAutofillTitle" }, - content: { key: "overrideDefaultBrowserAutofillDescription" }, - acceptButtonText: { key: "makeDefault" }, - acceptAction: async () => await this.handleOverrideDialogAccept(), - cancelButtonText: { key: "ignore" }, - type: "info", - }); - } - - async updateDefaultBrowserAutofillDisabled() { - const privacyPermissionGranted = await this.privacyPermissionGranted(); - if (!this.defaultBrowserAutofillDisabled && !privacyPermissionGranted) { - return; - } - - if ( - !privacyPermissionGranted && - !(await BrowserApi.requestPermission({ permissions: ["privacy"] })) - ) { - await this.dialogService.openSimpleDialog({ - title: { key: "privacyPermissionAdditionNotGrantedTitle" }, - content: { key: "privacyPermissionAdditionNotGrantedDescription" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "warning", - }); - this.defaultBrowserAutofillDisabled = false; - - return; - } - - BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled); - } - - private handleOverrideDialogAccept = async () => { - this.defaultBrowserAutofillDisabled = true; - await this.updateDefaultBrowserAutofillDisabled(); - }; - - async browserAutofillSettingCurrentlyOverridden() { - if (!this.canOverrideBrowserAutofillSetting) { - return false; - } - - if (!(await this.privacyPermissionGranted())) { - return false; - } - - return await BrowserApi.browserAutofillSettingsOverridden(); - } - - async privacyPermissionGranted(): Promise { - return await BrowserApi.permissionsGranted(["privacy"]); - } - - async updateContextMenuItem() { - await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem); - this.messagingService.send("bgUpdateContextMenu"); - } - - async updateAutoTotpCopy() { - await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy); - } - - async saveClearClipboard() { - await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard); - } - - async updateShowCardsCurrentTab() { - await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab); - } - - async updateShowIdentitiesCurrentTab() { - await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab); - } - - async updateShowInlineMenuCards() { - await this.autofillSettingsService.setShowInlineMenuCards(this.showInlineMenuCards); - } - - async updateShowInlineMenuIdentities() { - await this.autofillSettingsService.setShowInlineMenuIdentities(this.showInlineMenuIdentities); - } -} diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html deleted file mode 100644 index 8f78ac16404..00000000000 --- a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html +++ /dev/null @@ -1,91 +0,0 @@ - -
    -
    - -
    -

    - {{ "excludedDomains" | i18n }} -

    -
    - -
    -
    -
    -
    -
    - - -
    - -
    - - - - -
    -
    - -
    -
    -
    - -
    -
    -
    - diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts deleted file mode 100644 index 1a7dcd9c2dc..00000000000 --- a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts +++ /dev/null @@ -1,143 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; - -interface ExcludedDomain { - uri: string; - showCurrentUris: boolean; -} - -const BroadcasterSubscriptionId = "excludedDomains"; - -@Component({ - selector: "app-excluded-domains-v1", - templateUrl: "excluded-domains-v1.component.html", -}) -export class ExcludedDomainsV1Component implements OnInit, OnDestroy { - excludedDomains: ExcludedDomain[] = []; - existingExcludedDomains: ExcludedDomain[] = []; - currentUris: string[]; - loadCurrentUrisTimeout: number; - accountSwitcherEnabled = false; - - constructor( - private domainSettingsService: DomainSettingsService, - private i18nService: I18nService, - private router: Router, - private broadcasterService: BroadcasterService, - private ngZone: NgZone, - private platformUtilsService: PlatformUtilsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); - if (savedDomains) { - for (const uri of Object.keys(savedDomains)) { - this.excludedDomains.push({ uri: uri, showCurrentUris: false }); - this.existingExcludedDomains.push({ uri: uri, showCurrentUris: false }); - } - } - - await this.loadCurrentUris(); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "tabChanged": - case "windowChanged": - if (this.loadCurrentUrisTimeout != null) { - window.clearTimeout(this.loadCurrentUrisTimeout); - } - this.loadCurrentUrisTimeout = window.setTimeout( - async () => await this.loadCurrentUris(), - 500, - ); - break; - default: - break; - } - }); - }); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - async addUri() { - this.excludedDomains.push({ uri: "", showCurrentUris: false }); - } - - async removeUri(i: number) { - this.excludedDomains.splice(i, 1); - } - - async submit() { - const savedDomains: { [name: string]: null } = {}; - const newExcludedDomains = this.getNewlyAddedDomains(this.excludedDomains); - for (const domain of this.excludedDomains) { - const resp = newExcludedDomains.filter((e) => e.uri === domain.uri); - if (resp.length === 0) { - savedDomains[domain.uri] = null; - } else { - if (domain.uri && domain.uri !== "") { - const validDomain = Utils.getHostname(domain.uri); - if (!validDomain) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", domain.uri), - ); - return; - } - savedDomains[validDomain] = null; - } - } - } - - await this.domainSettingsService.setNeverDomains(savedDomains); - // 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(["/tabs/settings"]); - } - - trackByFunction(index: number, item: any) { - return index; - } - - getNewlyAddedDomains(domain: ExcludedDomain[]): ExcludedDomain[] { - const result = this.excludedDomains.filter( - (newDomain) => - !this.existingExcludedDomains.some((oldDomain) => newDomain.uri === oldDomain.uri), - ); - return result; - } - - toggleUriInput(domain: ExcludedDomain) { - domain.showCurrentUris = !domain.showCurrentUris; - } - - async loadCurrentUris() { - const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); - if (tabs) { - const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url))); - uriSet.delete(null); - this.currentUris = Array.from(uriSet); - } - } -} diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html b/apps/browser/src/autofill/popup/settings/notifications-v1.component.html deleted file mode 100644 index 89d83c9e480..00000000000 --- a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html +++ /dev/null @@ -1,89 +0,0 @@ -
    -
    - -
    -

    - {{ "notifications" | i18n }} -

    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    - -
    -
    -
    diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts deleted file mode 100644 index 442e91e55eb..00000000000 --- a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; - -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "autofill-notification-v1-settings", - templateUrl: "notifications-v1.component.html", -}) -export class NotificationsSettingsV1Component implements OnInit { - enableAddLoginNotification = false; - enableChangedPasswordNotification = false; - enablePasskeys = true; - accountSwitcherEnabled = false; - - constructor( - private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, - private vaultSettingsService: VaultSettingsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - this.enableAddLoginNotification = await firstValueFrom( - this.userNotificationSettingsService.enableAddedLoginPrompt$, - ); - - this.enableChangedPasswordNotification = await firstValueFrom( - this.userNotificationSettingsService.enableChangedPasswordPrompt$, - ); - - this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); - } - - async updateAddLoginNotification() { - await this.userNotificationSettingsService.setEnableAddedLoginPrompt( - this.enableAddLoginNotification, - ); - } - - async updateChangedPasswordNotification() { - await this.userNotificationSettingsService.setEnableChangedPasswordPrompt( - this.enableChangedPasswordNotification, - ); - } - - async updateEnablePasskeys() { - await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys); - } -} diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 863ca26b36e..38bb2ec50c9 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -259,9 +259,7 @@ export default class RuntimeBackground { await this.main.refreshBadge(); await this.main.refreshMenu(false); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); break; } case "addToLockedVaultPendingNotifications": @@ -288,9 +286,7 @@ export default class RuntimeBackground { await this.configService.ensureConfigFetched(); await this.main.updateOverlayCiphers(); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); } break; case "openPopup": diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 8e48104737a..d55bebfa0c3 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -17,7 +17,6 @@ import { unauthGuardFn, } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap"; import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { @@ -71,14 +70,10 @@ import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component" import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; -import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; -import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-domains.component"; -import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; -import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; @@ -148,11 +143,12 @@ const routes: Routes = [ canActivate: [unauthGuardFn(unauthRouteOverrides), unauthUiRefreshRedirect("/login")], data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(Fido2V1Component, Fido2Component, { + { path: "fido2", + component: Fido2Component, canActivate: [fido2AuthGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, ...twofactorRefactorSwap( TwoFactorComponent, AnonLayoutWrapperComponent, @@ -321,22 +317,24 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(AutofillV1Component, AutofillComponent, { + { path: "autofill", + component: AutofillComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "account-security", component: AccountSecurityComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, { + { path: "notifications", + component: NotificationsSettingsComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "vault-settings", component: VaultSettingsV2Component, @@ -355,11 +353,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { + { path: "excluded-domains", + component: ExcludedDomainsComponent, canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, - }), + }, { path: "premium", component: PremiumV2Component, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 1fe8f1f18db..15a898aef53 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -35,17 +35,7 @@ import { SsoComponentV1 } from "../auth/popup/sso-v1.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; -import { Fido2CipherRowV1Component } from "../autofill/popup/fido2/fido2-cipher-row-v1.component"; -import { Fido2CipherRowComponent } from "../autofill/popup/fido2/fido2-cipher-row.component"; -import { Fido2UseBrowserLinkV1Component } from "../autofill/popup/fido2/fido2-use-browser-link-v1.component"; -import { Fido2UseBrowserLinkComponent } from "../autofill/popup/fido2/fido2-use-browser-link.component"; -import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; -import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; -import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; -import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; -import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; -import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; import { HeaderComponent } from "../platform/popup/header.component"; @@ -87,10 +77,6 @@ import "../platform/popup/locales"; ScrollingModule, ServicesModule, DialogModule, - ExcludedDomainsComponent, - Fido2CipherRowComponent, - Fido2Component, - Fido2UseBrowserLinkComponent, FilePopoutCalloutComponent, AvatarModule, AccountComponent, @@ -112,15 +98,11 @@ import "../platform/popup/locales"; ColorPasswordPipe, ColorPasswordCountPipe, EnvironmentComponent, - ExcludedDomainsV1Component, - Fido2CipherRowV1Component, - Fido2UseBrowserLinkV1Component, HintComponent, HomeComponent, LoginViaAuthRequestComponentV1, LoginComponentV1, LoginDecryptionOptionsComponentV1, - NotificationsSettingsV1Component, RegisterComponent, SetPasswordComponent, SsoComponentV1, @@ -131,8 +113,6 @@ import "../platform/popup/locales"; UserVerificationComponent, VaultTimeoutInputComponent, RemovePasswordComponent, - Fido2V1Component, - AutofillV1Component, EnvironmentSelectorComponent, ], exports: [], From fb4d7e8f056bdc95b284ee06b99ead5d89524bab Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Mon, 13 Jan 2025 09:01:16 -0500 Subject: [PATCH 150/270] fix broken blockedInteractionsUris state contruction (#12813) --- .../platform/services/browser-script-injector.service.spec.ts | 2 +- .../src/tools/background/fileless-importer.background.spec.ts | 2 +- .../src/autofill/services/domain-settings.service.spec.ts | 2 +- libs/common/src/autofill/services/domain-settings.service.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts index 0919de46776..b177497305b 100644 --- a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts +++ b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts @@ -63,7 +63,7 @@ describe("ScriptInjectorService", () => { configService.getFeatureFlag$.mockImplementation(() => of(false)); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); - domainSettingsService.blockedInteractionsUris$ = of(null); + domainSettingsService.blockedInteractionsUris$ = of({}); scriptInjectorService = new BrowserScriptInjectorService( domainSettingsService, platformUtilsService, diff --git a/apps/browser/src/tools/background/fileless-importer.background.spec.ts b/apps/browser/src/tools/background/fileless-importer.background.spec.ts index 429a0e12184..409fac9790f 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.spec.ts @@ -39,7 +39,7 @@ describe("FilelessImporterBackground ", () => { let tabMock: chrome.tabs.Tab; beforeEach(() => { - domainSettingsService.blockedInteractionsUris$ = of(null); + domainSettingsService.blockedInteractionsUris$ = of({}); policyService.policyAppliesToActiveUser$.mockImplementation(() => of(true)); scriptInjectorService = new BrowserScriptInjectorService( domainSettingsService, diff --git a/libs/common/src/autofill/services/domain-settings.service.spec.ts b/libs/common/src/autofill/services/domain-settings.service.spec.ts index a25653f168c..5c5f85e7e67 100644 --- a/libs/common/src/autofill/services/domain-settings.service.spec.ts +++ b/libs/common/src/autofill/services/domain-settings.service.spec.ts @@ -29,7 +29,7 @@ describe("DefaultDomainSettingsService", () => { jest.spyOn(domainSettingsService, "getUrlEquivalentDomains"); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); - domainSettingsService.blockedInteractionsUris$ = of(null); + domainSettingsService.blockedInteractionsUris$ = of({}); }); describe("getUrlEquivalentDomains", () => { diff --git a/libs/common/src/autofill/services/domain-settings.service.ts b/libs/common/src/autofill/services/domain-settings.service.ts index aeb3af69dae..7d25c4e25d3 100644 --- a/libs/common/src/autofill/services/domain-settings.service.ts +++ b/libs/common/src/autofill/services/domain-settings.service.ts @@ -36,7 +36,7 @@ const BLOCKED_INTERACTIONS_URIS = new KeyDefinition( DOMAIN_SETTINGS_DISK, "blockedInteractionsUris", { - deserializer: (value: NeverDomains) => value ?? null, + deserializer: (value: NeverDomains) => value ?? {}, }, ); @@ -131,7 +131,7 @@ export class DefaultDomainSettingsService implements DomainSettingsService { switchMap((featureIsEnabled) => featureIsEnabled ? this.blockedInteractionsUrisState.state$ : of({} as NeverDomains), ), - map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : null)), + map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : {})), ); this.equivalentDomainsState = this.stateProvider.getActive(EQUIVALENT_DOMAINS); From 8062475044bea54af8a02c5d7cb6a0cfaacc8254 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Mon, 13 Jan 2025 10:37:26 -0500 Subject: [PATCH 151/270] [PM-16243] Allow users to collapse extension sections (#12756) --- .../vault-list-items-container.component.html | 87 +++++++++--- .../vault-list-items-container.component.ts | 66 ++++++++- .../vault-v2/vault-v2.component.html | 2 + .../services/vault-popup-section.service.ts | 129 ++++++++++++++++++ .../src/section/section-header.component.html | 2 +- 5 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 apps/browser/src/vault/popup/services/vault-popup-section.service.ts 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 b5d92a386b3..293bf3e67e4 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 @@ -1,23 +1,76 @@ -
    - -

    - {{ title }} -

    - - {{ ciphers.length }} -
    -
    + + + + + + + + +
    + +
    + + +
    +
    + + + +

    + {{ title }} +

    + + + + {{ ciphers.length }} + + + + + +
    +
    + +
    {{ description }}
    +
    + + - + 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 5bcdaf56bbd..579cbf6692a 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 @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ScrollingModule } from "@angular/cdk/scrolling"; +import { CdkVirtualScrollViewport, ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; import { AfterViewInit, @@ -9,8 +9,11 @@ import { EventEmitter, inject, Input, + OnInit, Output, + Signal, signal, + ViewChild, } from "@angular/core"; import { Router, RouterLink } from "@angular/router"; import { map } from "rxjs"; @@ -25,6 +28,8 @@ import { BadgeModule, ButtonModule, CompactModeService, + DisclosureComponent, + DisclosureTriggerForDirective, DialogService, IconButtonModule, ItemModule, @@ -41,6 +46,7 @@ import { import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; +import { VaultPopupSectionService } from "../../../services/vault-popup-section.service"; import { PopupCipherView } from "../../../views/popup-cipher.view"; import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component"; import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component"; @@ -61,14 +67,25 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options ItemMoreOptionsComponent, OrgIconDirective, ScrollingModule, + DisclosureComponent, + DisclosureTriggerForDirective, DecryptionFailureDialogComponent, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", standalone: true, }) -export class VaultListItemsContainerComponent implements AfterViewInit { +export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { private compactModeService = inject(CompactModeService); + private vaultPopupSectionService = inject(VaultPopupSectionService); + + @ViewChild(CdkVirtualScrollViewport, { static: false }) viewPort: CdkVirtualScrollViewport; + @ViewChild(DisclosureComponent) disclosure: DisclosureComponent; + + /** + * Indicates whether the section should be open or closed if collapsibleKey is provided + */ + protected sectionOpenState: Signal | undefined; /** * The class used to set the height of a bit item's inner content. @@ -106,6 +123,15 @@ export class VaultListItemsContainerComponent implements AfterViewInit { @Input() title: string; + /** + * Optionally allow the items to be collapsed. + * + * The key must be added to the state definition in `vault-popup-section.service.ts` since the + * collapsed state is stored locally. + */ + @Input() + collapsibleKey: "favorites" | "allItems" | undefined; + /** * Optional description for the vault list item section. Will be shown below the title even when * no ciphers are available. @@ -168,6 +194,16 @@ export class VaultListItemsContainerComponent implements AfterViewInit { private dialogService: DialogService, ) {} + ngOnInit(): void { + if (!this.collapsibleKey) { + return; + } + + this.sectionOpenState = this.vaultPopupSectionService.getOpenDisplayStateForSection( + this.collapsibleKey, + ); + } + async ngAfterViewInit() { const autofillShortcut = await this.platformUtilsService.getAutofillKeyboardShortcut(); @@ -239,4 +275,30 @@ export class VaultListItemsContainerComponent implements AfterViewInit { cipher.canLaunch ? 200 : 0, ); } + + /** + * Update section open/close state based on user action + */ + async toggleSectionOpen() { + if (!this.collapsibleKey) { + return; + } + + await this.vaultPopupSectionService.updateSectionOpenStoredState( + this.collapsibleKey, + this.disclosure.open, + ); + } + + /** + * Force virtual scroll to update its viewport size to avoid display bugs + * + * Angular CDK scroll has a bug when used with conditional rendering: + * https://github.com/angular/components/issues/24362 + */ + protected rerenderViewport() { + setTimeout(() => { + this.viewPort.checkViewportSize(); + }); + } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 4798ddf4dfb..c5a3c860c86 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -66,12 +66,14 @@ [title]="'favorites' | i18n" [ciphers]="favoriteCiphers$ | async" id="favorites" + collapsibleKey="favorites" > diff --git a/apps/browser/src/vault/popup/services/vault-popup-section.service.ts b/apps/browser/src/vault/popup/services/vault-popup-section.service.ts new file mode 100644 index 00000000000..ed641e0cdf7 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-section.service.ts @@ -0,0 +1,129 @@ +import { computed, effect, inject, Injectable, signal, Signal } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; + +import { + KeyDefinition, + StateProvider, + VAULT_SETTINGS_DISK, +} from "@bitwarden/common/platform/state"; + +import { VaultPopupItemsService } from "./vault-popup-items.service"; + +export type PopupSectionOpen = { + favorites: boolean; + allItems: boolean; +}; + +const SECTION_OPEN_KEY = new KeyDefinition(VAULT_SETTINGS_DISK, "sectionOpen", { + deserializer: (obj) => obj, +}); + +const INITIAL_OPEN: PopupSectionOpen = { + favorites: true, + allItems: true, +}; + +@Injectable({ + providedIn: "root", +}) +export class VaultPopupSectionService { + private vaultPopupItemsService = inject(VaultPopupItemsService); + private stateProvider = inject(StateProvider); + + private hasFilterOrSearchApplied = toSignal( + this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => hasFilter)), + ); + + /** + * Used to change the open/close state without persisting it to the local disk. Reflects + * application-applied overrides. + * `null` means there is no current override + */ + private temporaryStateOverride = signal | null>(null); + + constructor() { + effect( + () => { + /** + * auto-open all sections when search or filter is applied, and remove + * override when search or filter is removed + */ + if (this.hasFilterOrSearchApplied()) { + this.temporaryStateOverride.set(INITIAL_OPEN); + } else { + this.temporaryStateOverride.set(null); + } + }, + { + allowSignalWrites: true, + }, + ); + } + + /** + * Stored disk state for the open/close state of the sections. Will be `null` if user has never + * opened/closed a section + */ + private sectionOpenStateProvider = this.stateProvider.getGlobal(SECTION_OPEN_KEY); + + /** + * Stored disk state for the open/close state of the sections, with an initial value provided + * if the stored disk state does not yet exist. + */ + private sectionOpenStoredState = toSignal( + this.sectionOpenStateProvider.state$.pipe(map((sectionOpen) => sectionOpen ?? INITIAL_OPEN)), + // Indicates that the state value is loading + { initialValue: null }, + ); + + /** + * Indicates the current open/close display state of each section, accounting for temporary + * non-persisted overrides. + */ + sectionOpenDisplayState: Signal> = computed(() => ({ + ...this.sectionOpenStoredState(), + ...this.temporaryStateOverride(), + })); + + /** + * Retrieve the open/close display state for a given section. + * + * @param sectionKey section key + */ + getOpenDisplayStateForSection(sectionKey: keyof PopupSectionOpen): Signal { + return computed(() => this.sectionOpenDisplayState()?.[sectionKey]); + } + + /** + * Updates the stored open/close state of a given section. Should be called only when a user action + * is taken directly to change the open/close state. + * + * Removes any current temporary override for the given section, as direct user action should + * supersede any application-applied overrides. + * + * @param sectionKey section key + */ + async updateSectionOpenStoredState( + sectionKey: keyof PopupSectionOpen, + open: boolean, + ): Promise { + await this.sectionOpenStateProvider.update((currentState) => { + return { + ...(currentState ?? INITIAL_OPEN), + [sectionKey]: open, + }; + }); + + this.temporaryStateOverride.update((prev) => { + if (prev !== null) { + return { + ...prev, + [sectionKey]: open, + }; + } + + return prev; + }); + } +} diff --git a/libs/components/src/section/section-header.component.html b/libs/components/src/section/section-header.component.html index 3f96e22540f..f070cfeae02 100644 --- a/libs/components/src/section/section-header.component.html +++ b/libs/components/src/section/section-header.component.html @@ -2,7 +2,7 @@
    -
    +
    From 52b6bfea1da5d67a69c92e1047479f4936b44f64 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:18:03 -0500 Subject: [PATCH 152/270] [PM-16104] [PM-15929] Org at risk members click on the card (#12732) * Org at risk members click on the card * Fixing at risk member counts * At risk member text modification * Changing ok button to close --- apps/web/src/locales/en/messages.json | 21 +++++++++++ .../risk-insights/models/password-health.ts | 10 ++++++ .../services/risk-insights-report.service.ts | 25 +++++++++++++ .../all-applications.component.html | 5 +-- .../all-applications.component.ts | 19 ++++++++++ .../app-at-risk-members-dialog.component.html | 21 +++++++++++ .../app-at-risk-members-dialog.component.ts | 35 +++++++++++++++++++ .../org-at-risk-members-dialog.component.html | 27 ++++++++++++++ .../org-at-risk-members-dialog.component.ts | 24 +++++++++++++ 9 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 001918ef495..0181d4f9c78 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -113,6 +113,27 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index b8d5852088a..ad87f319e73 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -90,3 +90,13 @@ export type MemberDetailsFlat = { email: string; cipherId: string; }; + +/** + * Member email with the number of at risk passwords + * At risk member detail that contains the email + * and the count of at risk ciphers + */ +export type AtRiskMemberDetail = { + email: string; + atRiskPasswordCount: number; +}; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts index d0530dfd821..d97550b5887 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts @@ -12,6 +12,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ApplicationHealthReportDetail, ApplicationHealthReportSummary, + AtRiskMemberDetail, CipherHealthReportDetail, CipherHealthReportUriDetail, ExposedPasswordDetail, @@ -89,6 +90,30 @@ export class RiskInsightsReportService { return results$; } + /** + * Generates a list of members with at-risk passwords along with the number of at-risk passwords. + */ + generateAtRiskMemberList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskMemberDetail[] { + const memberRiskMap = new Map(); + + cipherHealthReportDetails.forEach((app) => { + app.atRiskMemberDetails.forEach((member) => { + if (memberRiskMap.has(member.email)) { + memberRiskMap.set(member.email, memberRiskMap.get(member.email) + 1); + } else { + memberRiskMap.set(member.email, 1); + } + }); + }); + + return Array.from(memberRiskMap.entries()).map(([email, atRiskPasswordCount]) => ({ + email, + atRiskPasswordCount, + })); + } + /** * Gets the summary from the application health report. Returns total members and applications as well * as the total at risk members and at risk applications diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html index ea1a4f9db31..0493f7e44b8 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html @@ -27,10 +27,11 @@

    {{ "allApplications" | i18n }}

    - + {{ r.applicationName }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index f4d3656071d..00708de282f 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -19,6 +19,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { + DialogService, Icons, NoItemsModule, SearchModule, @@ -30,6 +31,8 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; +import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component"; +import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @Component({ @@ -99,6 +102,7 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { protected dataService: RiskInsightsDataService, protected organizationService: OrganizationService, protected reportService: RiskInsightsReportService, + protected dialogService: DialogService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -135,6 +139,21 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { return item.applicationName; } + showAppAtRiskMembers = async (applicationName: string) => { + openAppAtRiskMembersDialog(this.dialogService, { + members: + this.dataSource.data.find((app) => app.applicationName === applicationName) + ?.atRiskMemberDetails ?? [], + applicationName, + }); + }; + + showOrgAtRiskMembers = async () => { + this.dialogService.open(OrgAtRiskMembersDialogComponent, { + data: this.reportService.generateAtRiskMemberList(this.dataSource.data), + }); + }; + onCheckboxChange(id: number, event: Event) { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html new file mode 100644 index 00000000000..383a1eccabe --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html @@ -0,0 +1,21 @@ + + {{ applicationName }} + +
    + {{ "atRiskMembersWithCount" | i18n: members.length }} + {{ + "atRiskMembersDescriptionWithApp" | i18n: applicationName + }} +
    + +
    {{ member.email }}
    +
    +
    +
    +
    + + + +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts new file mode 100644 index 00000000000..d6a757fe897 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts @@ -0,0 +1,35 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { MemberDetailsFlat } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; + +type AppAtRiskMembersDialogParams = { + members: MemberDetailsFlat[]; + applicationName: string; +}; + +export const openAppAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AppAtRiskMembersDialogParams, +) => + dialogService.open(AppAtRiskMembersDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./app-at-risk-members-dialog.component.html", + imports: [ButtonModule, CommonModule, JslibModule, DialogModule], +}) +export class AppAtRiskMembersDialogComponent { + protected members: MemberDetailsFlat[]; + protected applicationName: string; + + constructor(@Inject(DIALOG_DATA) private params: AppAtRiskMembersDialogParams) { + this.members = params.members; + this.applicationName = params.applicationName; + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html new file mode 100644 index 00000000000..41ac8af7886 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html @@ -0,0 +1,27 @@ + + + {{ "atRiskMembersWithCount" | i18n: atRiskMembers.length }} + + +
    + {{ + "atRiskMembersDescription" | i18n + }} +
    +
    {{ "email" | i18n }}
    +
    {{ "atRiskPasswords" | i18n }}
    +
    + +
    +
    {{ member.email }}
    +
    {{ member.atRiskPasswordCount }}
    +
    +
    +
    +
    + + + +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts new file mode 100644 index 00000000000..72518843d94 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts @@ -0,0 +1,24 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AtRiskMemberDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; + +export const openOrgAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AtRiskMemberDetail[], +) => + dialogService.open(OrgAtRiskMembersDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./org-at-risk-members-dialog.component.html", + imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], +}) +export class OrgAtRiskMembersDialogComponent { + constructor(@Inject(DIALOG_DATA) protected atRiskMembers: AtRiskMemberDetail[]) {} +} From 698479eb5ac2eaa225eb05aa694127953a36cf41 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:12:24 +0100 Subject: [PATCH 153/270] [PM-15814]Alert owners of reseller-managed orgs to renewal events (#12834) * Makes changes on the text * Rename the message names --- .../app/billing/services/reseller-warning.service.ts | 6 +++--- apps/web/src/locales/en/messages.json | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/web/src/app/billing/services/reseller-warning.service.ts b/apps/web/src/app/billing/services/reseller-warning.service.ts index bfd5be3233a..2c59ebafe05 100644 --- a/apps/web/src/app/billing/services/reseller-warning.service.ts +++ b/apps/web/src/app/billing/services/reseller-warning.service.ts @@ -33,7 +33,7 @@ export class ResellerWarningService { return { type: "warning", message: this.i18nService.t( - "resellerPastDueWarning", + "resellerPastDueWarningMsg", organization.providerName, this.formatDate(gracePeriodEnd), ), @@ -50,7 +50,7 @@ export class ResellerWarningService { return { type: "info", message: this.i18nService.t( - "resellerOpenInvoiceWarning", + "resellerOpenInvoiceWarningMgs", organization.providerName, this.formatDate(organizationBillingMetadata.invoiceCreatedDate), this.formatDate(organizationBillingMetadata.invoiceDueDate), @@ -68,7 +68,7 @@ export class ResellerWarningService { return { type: "info", message: this.i18nService.t( - "resellerRenewalWarning", + "resellerRenewalWarningMsg", organization.providerName, this.formatDate(organizationBillingMetadata.subPeriodEndDate), ), diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 0181d4f9c78..4307dba71e9 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10104,8 +10104,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10117,8 +10117,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10134,8 +10134,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", From 3bed613a91a688d1e76a9d6ef45d1f46a3282bbe Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 13 Jan 2025 18:35:58 +0100 Subject: [PATCH 154/270] Uninstall Angular elements (#12752) --- package-lock.json | 17 ----------------- package.json | 1 - 2 files changed, 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 591478d3516..73e7c41ebd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,7 +83,6 @@ "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", - "@angular/elements": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", @@ -2287,22 +2286,6 @@ "zone.js": "~0.14.10" } }, - "node_modules/@angular/elements": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-18.2.13.tgz", - "integrity": "sha512-yahRkXWgFolpWMeVsTIlWYwoq4Bsz6wrfS4b+TKHIZbTCyRUlJ5zBFecpYMwgmVuQDJZp+WkUWZV2Qg7kLJR5w==", - "dev": true, - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.13", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, "node_modules/@angular/forms": { "version": "18.2.13", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", diff --git a/package.json b/package.json index 3dcb2c142ed..ef55a938831 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", - "@angular/elements": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", From 459fb1bcf4c7378acde4a507aeb881d38f73efa7 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Mon, 13 Jan 2025 09:58:52 -0800 Subject: [PATCH 155/270] [PM-5718] Fix free organization generating TOTP (#11918) * [PM-5718] Fix totp generation for free orgs in old add-edit component * [PM-5718] Fix totp generation for free orgs in view cipher view component * [PM-5718] Cleanup merge conflicts * Don't generate totp code for premium users or free orgs * Added redirect to organization helper page * Changed text to learn more * Only show upgrade message to premium users * Show upgrade message to free users with free orgs as well --------- Co-authored-by: Matt Bishop Co-authored-by: gbubemismith --- apps/desktop/src/app/app.component.ts | 14 +++++++ apps/desktop/src/locales/en/messages.json | 9 +++++ .../src/vault/app/vault/view.component.html | 10 +++++ .../src/vault/app/vault/view.component.ts | 6 +++ .../individual-vault/add-edit.component.ts | 5 ++- .../vault/components/add-edit.component.ts | 2 +- .../src/vault/components/view.component.ts | 16 +++++--- .../login-credentials-view.component.html | 8 ++-- .../login-credentials-view.component.ts | 38 ++++++++++++++++--- 9 files changed, 89 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 5fefbf9ddff..a05b09e139e 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -330,6 +330,20 @@ export class AppComponent implements OnInit, OnDestroy { } break; } + case "upgradeOrganization": { + const upgradeConfirmed = await this.dialogService.openSimpleDialog({ + title: { key: "upgradeOrganization" }, + content: { key: "upgradeOrganizationDesc" }, + acceptButtonText: { key: "learnMore" }, + type: "info", + }); + if (upgradeConfirmed) { + this.platformUtilsService.launchUri( + "https://bitwarden.com/help/upgrade-from-individual-to-org/", + ); + } + break; + } case "emailVerificationRequired": { const emailVerificationConfirmed = await this.dialogService.openSimpleDialog({ title: { key: "emailVerificationRequired" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 1a02e5db4e7..bca12f16a7d 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/vault/app/vault/view.component.html b/apps/desktop/src/vault/app/vault/view.component.html index 59e609312d7..f589ba53046 100644 --- a/apps/desktop/src/vault/app/vault/view.component.html +++ b/apps/desktop/src/vault/app/vault/view.component.html @@ -186,6 +186,16 @@
    +
    +
    + {{ "verificationCodeTotp" | i18n }} + + {{ "organizationUpgradeRequired" | i18n }} + + +
    +
    diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 23b85eceda2..d3e8fff3495 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -157,4 +157,10 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.messagingService.send("premiumRequired"); } } + + upgradeOrganization() { + this.messagingService.send("upgradeOrganization", { + organizationId: this.cipher.organizationId, + }); + } } diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 56db7dc88da..916c845e9d3 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -66,7 +66,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On protected messagingService: MessagingService, eventCollectionService: EventCollectionService, protected policyService: PolicyService, - organizationService: OrganizationService, + protected organizationService: OrganizationService, logService: LogService, passwordRepromptService: PasswordRepromptService, dialogService: DialogService, @@ -307,7 +307,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On this.cipher.type === CipherType.Login && this.cipher.login.totp && this.organization?.productTierType != ProductTierType.Free && - (this.cipher.organizationUseTotp || this.canAccessPremium) + ((this.canAccessPremium && this.cipher.organizationId == null) || + this.cipher.organizationUseTotp) ); } diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 8d286e0a3f9..bf2e68b71cd 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -128,7 +128,7 @@ export class AddEditComponent implements OnInit, OnDestroy { protected policyService: PolicyService, protected logService: LogService, protected passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService, + protected organizationService: OrganizationService, protected dialogService: DialogService, protected win: Window, protected datePipe: DatePipe, diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index fc12aeff2f2..724a1507be1 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -65,6 +65,7 @@ export class ViewComponent implements OnDestroy, OnInit { showPrivateKey: boolean; canAccessPremium: boolean; showPremiumRequiredTotp: boolean; + showUpgradeRequiredTotp: boolean; totpCode: string; totpCodeFormatted: string; totpDash: number; @@ -151,22 +152,25 @@ export class ViewComponent implements OnDestroy, OnInit { this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); this.showPremiumRequiredTotp = - this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp; + this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationId; this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ this.collectionId as CollectionId, ]); + this.showUpgradeRequiredTotp = + this.cipher.login.totp && this.cipher.organizationId && !this.cipher.organizationUseTotp; + if (this.cipher.folderId) { this.folder = await ( await firstValueFrom(this.folderService.folderViews$(activeUserId)) ).find((f) => f.id == this.cipher.folderId); } - if ( - this.cipher.type === CipherType.Login && - this.cipher.login.totp && - (cipher.organizationUseTotp || this.canAccessPremium) - ) { + const canGenerateTotp = this.cipher.organizationId + ? this.cipher.organizationUseTotp + : this.canAccessPremium; + + if (this.cipher.type === CipherType.Login && this.cipher.login.totp && canGenerateTotp) { await this.totpUpdateCode(); const interval = this.totpService.getTimeInterval(this.cipher.login.totp); await this.totpTick(interval); diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 8503604bf7c..b4a0d4841f8 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -116,7 +116,7 @@ {{ "verificationCodeTotp" | i18n }}
    diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 7533ac26471..281e187f78b 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -2,7 +2,15 @@ // @ts-strict-ignore import { CommonModule, DatePipe } from "@angular/common"; import { Component, inject, Input } from "@angular/core"; -import { Observable, switchMap } from "rxjs"; +import { + BehaviorSubject, + combineLatest, + filter, + map, + Observable, + shareReplay, + switchMap, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -12,13 +20,13 @@ import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { + BadgeModule, + ColorPasswordModule, FormFieldModule, + IconButtonModule, SectionComponent, SectionHeaderComponent, TypographyModule, - IconButtonModule, - BadgeModule, - ColorPasswordModule, } from "@bitwarden/components"; // FIXME: remove `src` and fix import @@ -51,13 +59,31 @@ type TotpCodeValues = { ], }) export class LoginCredentialsViewComponent { - @Input() cipher: CipherView; + @Input() + get cipher(): CipherView { + return this._cipher$.value; + } + set cipher(value: CipherView) { + this._cipher$.next(value); + } + private _cipher$ = new BehaviorSubject(null); - isPremium$: Observable = this.accountService.activeAccount$.pipe( + private _userHasPremium$: Observable = this.accountService.activeAccount$.pipe( switchMap((account) => this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ), ); + + allowTotpGeneration$: Observable = combineLatest([ + this._userHasPremium$, + this._cipher$.pipe(filter((c) => c != null)), + ]).pipe( + map(([userHasPremium, cipher]) => { + // User premium status only applies to personal ciphers, organizationUseTotp applies to organization ciphers + return (userHasPremium && cipher.organizationId == null) || cipher.organizationUseTotp; + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); showPasswordCount: boolean = false; passwordRevealed: boolean = false; totpCodeCopyObj: TotpCodeValues; From 7b11905db81588a2cb03753df157c60664152a1f Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 13 Jan 2025 18:59:01 +0100 Subject: [PATCH 156/270] Fix platform bad imports (#12838) --- .../src/app/secrets-manager/overview/overview.module.ts | 4 +--- .../projects/guards/project-access.guard.spec.ts | 4 +--- .../guards/service-account-access.guard.spec.ts | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts index b410bbec21e..94d9ab2d8ee 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts @@ -1,10 +1,8 @@ import { NgModule } from "@angular/core"; import { BannerModule } from "@bitwarden/components"; +import { OnboardingModule } from "@bitwarden/web-vault/app/shared/components/onboarding/onboarding.module"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { OnboardingModule } from "../../../../../../apps/web/src/app/shared/components/onboarding/onboarding.module"; import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { OverviewRoutingModule } from "./overview-routing.module"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 8447127fc91..78d367776d4 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -8,10 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +import { RouterService } from "@bitwarden/web-vault/app/core"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { RouterService } from "../../../../../../../apps/web/src/app/core/router.service"; import { ProjectView } from "../../models/view/project.view"; import { ProjectService } from "../project.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts index ef29f6be7ea..a4079a6def3 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -8,10 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +import { RouterService } from "@bitwarden/web-vault/app/core"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { RouterService } from "../../../../../../../../clients/apps/web/src/app/core/router.service"; import { ServiceAccountView } from "../../models/view/service-account.view"; import { ServiceAccountService } from "../service-account.service"; From be77489baaee68802bb87f497a3a8a99aac956dc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 13 Jan 2025 19:11:36 +0100 Subject: [PATCH 157/270] Remove differences from flathub flatpak manifest and devel manifest (#12555) --- apps/desktop/resources/com.bitwarden.desktop.devel.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml index 02f2474927e..fea28052f8d 100644 --- a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml +++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml @@ -8,7 +8,6 @@ command: bitwarden.sh finish-args: - --share=ipc - --share=network - - --socket=wayland - --socket=x11 - --device=dri - --env=XDG_CURRENT_DESKTOP=Unity @@ -43,5 +42,4 @@ modules: commands: - ulimit -c 0 - export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID" - - exec zypak-wrapper /app/bin/bitwarden-app --ozone-platform-hint=auto - --enable-features=WaylandWindowDecorations "$@" + - exec zypak-wrapper /app/bin/bitwarden-app "$@" From f28664716f2ea5cba813374d52d6c919c1482d4b Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Mon, 13 Jan 2025 19:26:50 +0100 Subject: [PATCH 158/270] [PM-16682] Provider setup tax id is not saved (#12678) --- .../app/admin-console/providers/setup/setup.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index f773db6c11c..ecf649b8f31 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -23,8 +23,7 @@ import { KeyService } from "@bitwarden/key-management"; templateUrl: "setup.component.html", }) export class SetupComponent implements OnInit, OnDestroy { - @ViewChild(ManageTaxInformationComponent) - manageTaxInformationComponent: ManageTaxInformationComponent; + @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent; loading = true; providerId: string; @@ -111,7 +110,7 @@ export class SetupComponent implements OnInit, OnDestroy { try { this.formGroup.markAllAsTouched(); - if (!this.manageTaxInformationComponent.validate() || !this.formGroup.valid) { + if (!this.taxInformationComponent.validate() || !this.formGroup.valid) { return; } @@ -125,7 +124,7 @@ export class SetupComponent implements OnInit, OnDestroy { request.key = key; request.taxInfo = new ExpandedTaxInfoUpdateRequest(); - const taxInformation = this.manageTaxInformationComponent.getTaxInformation(); + const taxInformation = this.taxInformationComponent.getTaxInformation(); request.taxInfo.country = taxInformation.country; request.taxInfo.postalCode = taxInformation.postalCode; @@ -147,6 +146,7 @@ export class SetupComponent implements OnInit, OnDestroy { await this.router.navigate(["/providers", provider.id]); } catch (e) { + e.message = this.i18nService.translate(e.message) || e.message; this.validationService.showError(e); } }; From ffe05f27c453df9b310a4cd9d3d5035fc245e8c3 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:45:44 +0100 Subject: [PATCH 159/270] Add csv export instructions for Nordpass (#12829) Co-authored-by: Daniel James Smith --- libs/importer/src/components/import.component.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 0da8127369e..25172f850b7 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -395,6 +395,11 @@ Open the FullClient, go to the Main Menu and select Export. Start the export passwords wizard and follow the instructions to export a CSV file. + + Log in to NordPass and open Settings → Scroll down to the Import and Export section + and select Export items → Enter your Master Password and select Continue. → Save + the CSV file on your device. + Date: Mon, 13 Jan 2025 19:59:59 +0100 Subject: [PATCH 160/270] [PM-16932] Fix timeout when desktop app is not started (#12799) * Fix biometrics button showing up when biometrics is not enabled * Fix tests * Fix timeout when desktop app is not started * Update comments for legacy biometrics removal --- .../src/background/nativeMessaging.background.ts | 2 +- .../lock/services/extension-lock-component.service.ts | 10 +++++++++- .../src/services/biometric-message-handler.service.ts | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 116d048d2e8..c134860f1a8 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -257,7 +257,7 @@ export class NativeMessagingBackground { message.command == BiometricsCommands.Unlock || message.command == BiometricsCommands.IsAvailable ) { - // TODO remove after 2025.01 + // TODO remove after 2025.3 // wait until there is no other callbacks, or timeout const call = await firstValueFrom( race( diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts index 77c0fcaf50a..711ae4e378f 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -54,7 +54,15 @@ export class ExtensionLockComponentService implements LockComponentService { if (!(await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$))) { return BiometricsStatus.NotEnabledLocally; } else { - return await this.biometricsService.getBiometricsStatusForUser(userId); + // TODO remove after 2025.3 + // remove after backward compatibility code for old biometrics ipc protocol is removed + const result: BiometricsStatus = (await Promise.race([ + this.biometricsService.getBiometricsStatusForUser(userId), + new Promise((resolve) => + setTimeout(() => resolve(BiometricsStatus.DesktopDisconnected), 1000), + ), + ])) as BiometricsStatus; + return result; } }), this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index ea1e7e76c56..72d00988e88 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -188,7 +188,7 @@ export class BiometricMessageHandlerService { appId, ); } - // TODO: legacy, remove after 2025.01 + // TODO: legacy, remove after 2025.3 case BiometricsCommands.IsAvailable: { const available = (await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available; @@ -200,7 +200,7 @@ export class BiometricMessageHandlerService { appId, ); } - // TODO: legacy, remove after 2025.01 + // TODO: legacy, remove after 2025.3 case BiometricsCommands.Unlock: { const isTemporarilyDisabled = (await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId)) && From d2523374747ce91ceca5715c139d40e1160f9b9f Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Mon, 13 Jan 2025 14:32:57 -0500 Subject: [PATCH 161/270] [pm-13645] invite member modal for free org references up to 20 users (#12837) * fix invite counter to show when there are 0 seats left --- .../member-dialog/member-dialog.component.html | 16 ++++++++++------ apps/web/src/locales/en/messages.json | 3 +++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index 3bef1b6ccc1..5e81e4ee711 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -23,15 +23,19 @@

    {{ "inviteUserDesc" | i18n }}

    - + {{ "email" | i18n }} - {{ - "inviteMultipleEmailDesc" | i18n: remainingSeats + + {{ + "inviteMultipleEmailDesc" | i18n: remaining.seats }} - - {{ "inviteSingleEmailDesc" | i18n: remainingSeats }} - + + {{ + "inviteSingleEmailDesc" | i18n + }} + + {{ "inviteZeroEmailDesc" | i18n }}
    diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 4307dba71e9..456981fe9a6 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3302,6 +3302,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, From 1fcdf25bf7c4a71ea3878e016e16c417adcce21c Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:39:48 -0500 Subject: [PATCH 162/270] Auth/PM-16947 - Web - Device Management - Add Manage Auth Requests support (#12809) * PM-16947 - JsLibServices - register default DefaultLoginApprovalComponentService * PM-16947 - DeviceResponse - add interface for DevicePendingAuthRequest * PM-16947 - Web translations - migrate all LoginApprovalComponent translations from desktop to web * PM-16947 - LoginApprovalComp - (1) Add loading state (2) Refactor to return proper boolean results (3) Don't create race condition by trying to respond to the close event in the dialog and re-sending responses upon approve or deny click * PM-16947 - DeviceManagementComponent - added support for approving and denying auth requests. * PM-16947 - LoginApprovalComp - Add validation error * PM-16947 - LoginApprovalComponent - remove validation service for now. * PM-16947 - Re add validation * PM-16947 - Fix LoginApprovalComponent tests --- .../security/device-management.component.html | 24 ++++++-- .../security/device-management.component.ts | 57 +++++++++++------ apps/web/src/locales/en/messages.json | 61 +++++++++++++++++++ .../src/services/jslib-services.module.ts | 7 +++ .../login-approval.component.html | 46 ++++++++------ .../login-approval.component.spec.ts | 4 ++ .../login-approval.component.ts | 21 ++++--- .../devices/responses/device.response.ts | 7 ++- 8 files changed, 178 insertions(+), 49 deletions(-) diff --git a/apps/web/src/app/auth/settings/security/device-management.component.html b/apps/web/src/app/auth/settings/security/device-management.component.html index 743414aac41..37827a33afe 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.html +++ b/apps/web/src/app/auth/settings/security/device-management.component.html @@ -48,17 +48,31 @@
    - {{ row.displayName }} - - {{ "trusted" | i18n }} - + + + {{ row.displayName }} + + + + {{ "needsApproval" | i18n }} + + + + {{ row.displayName }} + + {{ "trusted" | i18n }} + +
    {{ "currentSession" | i18n }} - {{ + {{ "requestPending" | i18n }} diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts index 65f2afc250e..e22122ad9ae 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.ts +++ b/apps/web/src/app/auth/settings/security/device-management.component.ts @@ -1,10 +1,14 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { firstValueFrom } from "rxjs"; -import { switchMap } from "rxjs/operators"; +import { combineLatest, firstValueFrom } from "rxjs"; +import { LoginApprovalComponent } from "@bitwarden/auth/angular"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; +import { + DevicePendingAuthRequest, + DeviceResponse, +} from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -26,7 +30,8 @@ interface DeviceTableData { loginStatus: string; firstLogin: Date; trusted: boolean; - devicePendingAuthRequest: object | null; + devicePendingAuthRequest: DevicePendingAuthRequest | null; + hasPendingAuthRequest: boolean; } /** @@ -52,28 +57,25 @@ export class DeviceManagementComponent { private toastService: ToastService, private validationService: ValidationService, ) { - this.devicesService - .getCurrentDevice$() - .pipe( - takeUntilDestroyed(), - switchMap((currentDevice) => { - this.currentDevice = new DeviceView(currentDevice); - return this.devicesService.getDevices$(); - }), - ) + combineLatest([this.devicesService.getCurrentDevice$(), this.devicesService.getDevices$()]) + .pipe(takeUntilDestroyed()) .subscribe({ - next: (devices) => { - this.dataSource.data = devices.map((device) => { + next: ([currentDevice, devices]: [DeviceResponse, Array]) => { + this.currentDevice = new DeviceView(currentDevice); + + this.dataSource.data = devices.map((device: DeviceView): DeviceTableData => { return { id: device.id, type: device.type, displayName: this.getHumanReadableDeviceType(device.type), loginStatus: this.getLoginStatus(device), - devicePendingAuthRequest: device.response.devicePendingAuthRequest, firstLogin: new Date(device.creationDate), trusted: device.response.isTrusted, + devicePendingAuthRequest: device.response.devicePendingAuthRequest, + hasPendingAuthRequest: this.hasPendingAuthRequest(device.response), }; }); + this.loading = false; }, error: () => { @@ -176,15 +178,36 @@ export class DeviceManagementComponent { /** * Check if a device has a pending auth request - * @param device - The device + * @param device - The device response * @returns True if the device has a pending auth request, false otherwise */ - protected hasPendingAuthRequest(device: DeviceTableData): boolean { + private hasPendingAuthRequest(device: DeviceResponse): boolean { return ( device.devicePendingAuthRequest !== undefined && device.devicePendingAuthRequest !== null ); } + /** + * Open a dialog to approve or deny a pending auth request for a device + */ + async managePendingAuthRequest(device: DeviceTableData) { + if (device.devicePendingAuthRequest === undefined || device.devicePendingAuthRequest === null) { + return; + } + + const dialogRef = LoginApprovalComponent.open(this.dialogService, { + notificationId: device.devicePendingAuthRequest.id, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result !== undefined && typeof result === "boolean") { + // auth request approved or denied so reset + device.devicePendingAuthRequest = null; + device.hasPendingAuthRequest = false; + } + } + /** * Remove a device * @param device - The device diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 456981fe9a6..2779c0470e7 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3813,6 +3813,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 93d25af1a53..803808612cf 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -20,6 +20,7 @@ import { DefaultLoginComponentService, LoginDecryptionOptionsService, DefaultLoginDecryptionOptionsService, + DefaultLoginApprovalComponentService, } from "@bitwarden/auth/angular"; import { AuthRequestServiceAbstraction, @@ -39,6 +40,7 @@ import { DefaultAuthRequestApiService, DefaultLoginSuccessHandlerService, LoginSuccessHandlerService, + LoginApprovalComponentServiceAbstraction, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; @@ -1405,6 +1407,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultAuthRequestApiService, deps: [ApiServiceAbstraction, LogService], }), + safeProvider({ + provide: LoginApprovalComponentServiceAbstraction, + useClass: DefaultLoginApprovalComponentService, + deps: [], + }), safeProvider({ provide: LoginDecryptionOptionsService, useClass: DefaultLoginDecryptionOptionsService, diff --git a/libs/auth/src/angular/login-approval/login-approval.component.html b/libs/auth/src/angular/login-approval/login-approval.component.html index ddbc48d71a3..c0cb9b9caf4 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.html +++ b/libs/auth/src/angular/login-approval/login-approval.component.html @@ -1,23 +1,31 @@ {{ "areYouTryingtoLogin" | i18n }} -

    {{ "logInAttemptBy" | i18n: email }}

    -
    - {{ "fingerprintPhraseHeader" | i18n }} -

    {{ fingerprintPhrase }}

    -
    -
    - {{ "deviceType" | i18n }} -

    {{ authRequestResponse?.requestDeviceType }}

    -
    -
    - {{ "ipAddress" | i18n }} -

    {{ authRequestResponse?.requestIpAddress }}

    -
    -
    - {{ "time" | i18n }} -

    {{ requestTimeText }}

    -
    + +
    + +
    +
    + + +

    {{ "logInAttemptBy" | i18n: email }}

    +
    + {{ "fingerprintPhraseHeader" | i18n }} +

    {{ fingerprintPhrase }}

    +
    +
    + {{ "deviceType" | i18n }} +

    {{ authRequestResponse?.requestDeviceType }}

    +
    +
    + {{ "ipAddress" | i18n }} +

    {{ authRequestResponse?.requestIpAddress }}

    +
    +
    + {{ "time" | i18n }} +

    {{ requestTimeText }}

    +
    +
    @@ -34,7 +42,7 @@ type="button" buttonType="secondary" [bitAction]="denyLogin" - [bitDialogClose]="true" + [disabled]="loading" > {{ "denyLogIn" | i18n }} diff --git a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts index ff598bdeb91..da30df62fff 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts @@ -13,6 +13,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -29,6 +30,7 @@ describe("LoginApprovalComponent", () => { let i18nService: MockProxy; let dialogRef: MockProxy; let toastService: MockProxy; + let validationService: MockProxy; const testNotificationId = "test-notification-id"; const testEmail = "test@bitwarden.com"; @@ -41,6 +43,7 @@ describe("LoginApprovalComponent", () => { i18nService = mock(); dialogRef = mock(); toastService = mock(); + validationService = mock(); accountService.activeAccount$ = of({ email: testEmail, @@ -62,6 +65,7 @@ describe("LoginApprovalComponent", () => { { provide: KeyService, useValue: mock() }, { provide: DialogRef, useValue: dialogRef }, { provide: ToastService, useValue: toastService }, + { provide: ValidationService, useValue: validationService }, { provide: LoginApprovalComponentServiceAbstraction, useValue: mock(), diff --git a/libs/auth/src/angular/login-approval/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts index 5192334a0ca..3b44f545abb 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -16,6 +16,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { AsyncActionsModule, @@ -40,6 +41,8 @@ export interface LoginApprovalDialogParams { imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule], }) export class LoginApprovalComponent implements OnInit, OnDestroy { + loading = true; + notificationId: string; private destroy$ = new Subject(); @@ -62,25 +65,25 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { private dialogRef: DialogRef, private toastService: ToastService, private loginApprovalComponentService: LoginApprovalComponentService, + private validationService: ValidationService, ) { this.notificationId = params.notificationId; } async ngOnDestroy(): Promise { clearInterval(this.interval); - const closedWithButton = await firstValueFrom(this.dialogRef.closed); - if (!closedWithButton) { - // 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.retrieveAuthRequestAndRespond(false); - } this.destroy$.next(); this.destroy$.complete(); } async ngOnInit() { if (this.notificationId != null) { - this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + try { + this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + } catch (error) { + this.validationService.showError(error); + } + const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey); this.email = await await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), @@ -96,6 +99,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { }, RequestTimeUpdate); this.loginApprovalComponentService.showLoginRequestedAlertIfWindowNotVisible(this.email); + + this.loading = false; } } @@ -131,6 +136,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { ); this.showResultToast(loginResponse); } + + this.dialogRef.close(approve); } showResultToast(loginResponse: AuthRequestResponse) { diff --git a/libs/common/src/auth/abstractions/devices/responses/device.response.ts b/libs/common/src/auth/abstractions/devices/responses/device.response.ts index 707616744ad..84a2fb03c28 100644 --- a/libs/common/src/auth/abstractions/devices/responses/device.response.ts +++ b/libs/common/src/auth/abstractions/devices/responses/device.response.ts @@ -1,6 +1,11 @@ import { DeviceType } from "../../../../enums"; import { BaseResponse } from "../../../../models/response/base.response"; +export interface DevicePendingAuthRequest { + id: string; + creationDate: string; +} + export class DeviceResponse extends BaseResponse { id: string; userId: string; @@ -10,7 +15,7 @@ export class DeviceResponse extends BaseResponse { creationDate: string; revisionDate: string; isTrusted: boolean; - devicePendingAuthRequest: { id: string; creationDate: string } | null; + devicePendingAuthRequest: DevicePendingAuthRequest | null; constructor(response: any) { super(response); From 3f00f9eaf8aa4a8a7d9e6107afbdda6e78538af5 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Mon, 13 Jan 2025 14:43:34 -0500 Subject: [PATCH 163/270] [PM-16804] Add supporting Vault component presentational updates for blocked domains (#12720) * Revert "remove vault component presentational updates" This reverts commit fe40dd84644c10e932fb74f633c910293d9d04e8. * update vault popup autofill service to enable moving state closer to blocked domain component callsites * hide autofill actions from suggestions if the current tab location is on the blocklist * update autofill suggestions section title * update blocked domain section indicator tooltip message * create and use blocked-injection-banner component * update blocked URI banner with deeplink to settings * remove blocked URI indicator for suggestions section * fix suggested items showing cipher external link button * fix message catalog updates * move currentURIIsBlocked state fetching into VaultListItemsContainerComponent * leverage shareReplay caching for new state additions to VaultPopupAutofillService * have blocked-injection-banner component consume observable rather than init value * fix tests * use observables in the vault-list-items-container template --- apps/browser/src/_locales/en/messages.json | 12 ++-- .../autofill-vault-list-items.component.html | 2 +- .../blocked-injection-banner.component.html | 10 +++ .../blocked-injection-banner.component.ts | 53 +++++++++++++++ .../vault-list-items-container.component.html | 10 ++- .../vault-list-items-container.component.ts | 42 +++++++++++- .../vault-v2/vault-v2.component.html | 5 ++ .../components/vault-v2/vault-v2.component.ts | 2 + .../vault-popup-autofill.service.spec.ts | 3 + .../services/vault-popup-autofill.service.ts | 68 +++++++++++++++++++ 10 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.html create mode 100644 apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b72a909252b..51fb3a0a770 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4007,8 +4007,8 @@ "passkeyRemoved": { "message": "Passkey removed" }, - "autofillSuggestions": { - "message": "Autofill suggestions" + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" 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 7c4ea3e5b46..047d168ecbb 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 @@ -1,7 +1,7 @@ + {{ "autofillBlockedNoticeV2" | i18n }} + + {{ "autofillBlockedNoticeGuidance" | i18n }} + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts new file mode 100644 index 00000000000..3a17825f4fb --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts @@ -0,0 +1,53 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { Observable } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + BannerModule, + IconButtonModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; + +import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; + +const blockedURISettingsRoute = "/blocked-domains"; + +@Component({ + standalone: true, + imports: [ + BannerModule, + CommonModule, + IconButtonModule, + JslibModule, + LinkModule, + RouterModule, + TypographyModule, + ], + selector: "blocked-injection-banner", + templateUrl: "blocked-injection-banner.component.html", +}) +export class BlockedInjectionBanner implements OnInit { + /** + * Flag indicating that the banner should be shown + */ + protected showCurrentTabIsBlockedBanner$: Observable = + this.vaultPopupAutofillService.showCurrentTabIsBlockedBanner$; + + /** + * Hostname for current tab + */ + protected currentTabHostname?: string; + + blockedURISettingsRoute: string = blockedURISettingsRoute; + + constructor(private vaultPopupAutofillService: VaultPopupAutofillService) {} + + async ngOnInit() {} + + async handleCurrentTabIsBlockedBannerDismiss() { + await this.vaultPopupAutofillService.dismissCurrentTabIsBlockedBanner(); + } +} 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 293bf3e67e4..c55e8d9fb26 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 @@ -80,11 +80,9 @@ - +
    + + { // Create mocks for VaultPopupAutofillService const mockAutofillService = mock(); + const mockDomainSettingsService = mock(); const mockI18nService = mock(); const mockToastService = mock(); const mockPlatformUtilsService = mock(); @@ -71,6 +73,7 @@ describe("VaultPopupAutofillService", () => { testBed = TestBed.configureTestingModule({ providers: [ { provide: AutofillService, useValue: mockAutofillService }, + { provide: DomainSettingsService, useValue: mockDomainSettingsService }, { provide: I18nService, useValue: mockI18nService }, { provide: ToastService, useValue: mockToastService }, { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index 586e9182819..c0ac9c91e18 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -15,6 +15,7 @@ import { } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.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"; @@ -67,6 +68,72 @@ export class VaultPopupAutofillService { shareReplay({ refCount: false, bufferSize: 1 }), ); + currentTabIsOnBlocklist$: Observable = combineLatest([ + this.domainSettingsService.blockedInteractionsUris$, + this.currentAutofillTab$, + ]).pipe( + map(([blockedInteractionsUris, currentTab]) => { + if (blockedInteractionsUris && currentTab?.url?.length) { + const tabURL = new URL(currentTab.url); + const tabIsBlocked = Object.keys(blockedInteractionsUris).includes(tabURL.hostname); + + if (tabIsBlocked) { + return true; + } + } + + return false; + }), + shareReplay({ refCount: false, bufferSize: 1 }), + ); + + showCurrentTabIsBlockedBanner$: Observable = combineLatest([ + this.domainSettingsService.blockedInteractionsUris$, + this.currentAutofillTab$, + ]).pipe( + map(([blockedInteractionsUris, currentTab]) => { + if (blockedInteractionsUris && currentTab?.url?.length) { + const tabURL = new URL(currentTab.url); + const tabIsBlocked = Object.keys(blockedInteractionsUris).includes(tabURL.hostname); + + const showScriptInjectionIsBlockedBanner = + tabIsBlocked && !blockedInteractionsUris[tabURL.hostname]?.bannerIsDismissed; + + return showScriptInjectionIsBlockedBanner; + } + + return false; + }), + shareReplay({ refCount: false, bufferSize: 1 }), + ); + + async dismissCurrentTabIsBlockedBanner() { + try { + const currentTab = await firstValueFrom(this.currentAutofillTab$); + const currentTabURL = currentTab?.url.length && new URL(currentTab.url); + + const currentTabHostname = currentTabURL && currentTabURL.hostname; + + if (!currentTabHostname) { + return; + } + + const blockedURIs = await firstValueFrom(this.domainSettingsService.blockedInteractionsUris$); + const tabIsBlocked = Object.keys(blockedURIs).includes(currentTabHostname); + + if (tabIsBlocked) { + void this.domainSettingsService.setBlockedInteractionsUris({ + ...blockedURIs, + [currentTabHostname as string]: { bannerIsDismissed: true }, + }); + } + } catch (e) { + throw new Error( + "There was a problem dismissing the blocked interaction URI notification banner", + ); + } + } + /** * Observable that indicates whether autofill is allowed in the current context. * Autofill is allowed when there is a current tab and the popup is not in a popout window. @@ -125,6 +192,7 @@ export class VaultPopupAutofillService { constructor( private autofillService: AutofillService, + private domainSettingsService: DomainSettingsService, private i18nService: I18nService, private toastService: ToastService, private platformUtilService: PlatformUtilsService, From 83ee64ba1d3cb9d079ec67758bd0ce0a1cf79685 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 13 Jan 2025 20:04:22 +0000 Subject: [PATCH 164/270] Bumped client version(s) --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- apps/cli/package.json | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- apps/web/package.json | 2 +- package-lock.json | 8 ++++---- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index cf9309728ae..e4ec1fd5e6e 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.1.0", + "version": "2025.1.1", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 9e4eb78291a..86ea0eebbc8 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.1.0", + "version": "2025.1.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index ef7ede6b056..ae7c888eb9b 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.1.0", + "version": "2025.1.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/cli/package.json b/apps/cli/package.json index 28432bd5558..8a6dffb1cb3 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.0", + "version": "2025.1.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c55f39d1b2a..b8541aad2ec 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.0", + "version": "2025.1.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index f93d9059bc7..d8705487d86 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.1.1", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 39e16815020..95490ee34dd 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.0", + "version": "2025.1.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/web/package.json b/apps/web/package.json index 42432de29a4..73ecd8c3cb7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.1.0", + "version": "2025.1.1", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 73e7c41ebd6..d6f6b9d03a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -191,11 +191,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.1.0" + "version": "2025.1.1" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2025.1.0", + "version": "2025.1.1", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "3.0.2", @@ -231,7 +231,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.1.1", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -245,7 +245,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.1.0" + "version": "2025.1.1" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From d471fe0418ceb60cdd1fcd0caa1729462af9eb4f Mon Sep 17 00:00:00 2001 From: Evan Bassler Date: Mon, 13 Jan 2025 14:18:42 -0600 Subject: [PATCH 165/270] [PM-14954] implement multi input totp styling (#12449) * update menu and button position for multi-input totp * update test to better handle breaking changes * fix sizing bug by filtering duplicate opid fields * update getTotpFields usage * revert private changes per feedback * Update apps/browser/src/autofill/utils/index.ts with positive fucntion Co-authored-by: Jonathan Prusik * fix type and update rectNotZero function --------- Co-authored-by: Evan Bassler Co-authored-by: Jonathan Prusik Co-authored-by: Evan Bassler --- apps/browser/package.json | 3 +- .../abstractions/overlay.background.ts | 12 ++ .../background/overlay.background.spec.ts | 118 ++++++++++++++++++ .../autofill/background/overlay.background.ts | 92 +++++++++++++- .../src/autofill/models/autofill-field.ts | 6 + .../autofill-overlay-content.service.ts | 9 ++ apps/browser/src/autofill/utils/index.ts | 12 ++ 7 files changed, 248 insertions(+), 4 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index e4ec1fd5e6e..9ad1805362e 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -30,6 +30,7 @@ "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari", "test": "jest", "test:watch": "jest --watch", - "test:watch:all": "jest --watchAll" + "test:watch:all": "jest --watchAll", + "test:clearCache": "jest --clear-cache" } } diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 03284f3fd89..6ad9b8e06fd 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -57,6 +57,17 @@ export type InlineMenuElementPosition = { height: number; }; +export type FieldRect = { + bottom: number; + height: number; + left: number; + right: number; + top: number; + width: number; + x: number; + y: number; +}; + export type InlineMenuPosition = { button?: InlineMenuElementPosition; list?: InlineMenuElementPosition; @@ -134,6 +145,7 @@ export type OverlayBackgroundExtensionMessage = { isFieldCurrentlyFilling?: boolean; subFrameData?: SubFrameOffsetData; focusedFieldData?: FocusedFieldData; + allFieldsRect?: any; isOpeningFullInlineMenu?: boolean; styles?: Partial; data?: LockedVaultPendingNotificationsData; diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 0ac69317855..c3a6357ed05 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -2913,6 +2913,124 @@ describe("OverlayBackground", () => { ); }); }); + describe("handles menu position when input is focused", () => { + it("sets button and menu width and position when non-multi-input totp field is focused", async () => { + const subframe = { + top: 0, + left: 0, + url: "", + frameId: 0, + }; + + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + focusedFieldRects: { + width: 49.328125, + height: 64, + top: 302.171875, + left: 1270.8125, + }, + }); + + const buttonPostion = overlayBackground["getInlineMenuButtonPosition"](subframe); + const menuPostion = overlayBackground["getInlineMenuListPosition"](subframe); + + expect(menuPostion).toEqual({ + width: "49px", + top: "366px", + left: "1271px", + }); + expect(buttonPostion).toEqual({ + width: "34px", + height: "34px", + top: "317px", + left: "1271px", + }); + }); + it("sets button and menu width and position when multi-input totp field is focused", async () => { + const subframe = { + top: 0, + left: 0, + url: "", + frameId: 0, + }; + + const totpFields = [ + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__0" }), + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__1" }), + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__2" }), + ]; + const allFieldData = [ + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__0", + rect: { + x: 1041.5, + y: 302.171875, + width: 49.328125, + height: 64, + top: 302.171875, + right: 1090.828125, + bottom: 366.171875, + left: 1041.5, + }, + }), + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__1", + rect: { + x: 1098.828125, + y: 302.171875, + width: 49.328125, + height: 64, + top: 302.171875, + right: 1148.15625, + bottom: 366.171875, + left: 1098.828125, + }, + }), + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__2", + rect: { + x: 1156.15625, + y: 302.171875, + width: 249.328125, + height: 64, + top: 302.171875, + right: 2205.484375, + bottom: 366.171875, + left: 2156.15625, + }, + }), + ]; + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + focusedFieldRects: { + width: 49.328125, + height: 64, + top: 302.171875, + left: 1270.8125, + }, + }); + + overlayBackground["allFieldData"] = allFieldData; + jest.spyOn(overlayBackground as any, "isTotpFieldForCurrentField").mockReturnValue(true); + jest.spyOn(overlayBackground as any, "getTotpFields").mockReturnValue(totpFields); + + const buttonPostion = overlayBackground["getInlineMenuButtonPosition"](subframe); + const menuPostion = overlayBackground["getInlineMenuListPosition"](subframe); + expect(menuPostion).toEqual({ + width: "1164px", + top: "366px", + left: "1042px", + }); + expect(buttonPostion).toEqual({ + width: "34px", + height: "34px", + top: "292px", + left: "2187px", + }); + }); + }); describe("triggerDelayedAutofillInlineMenuClosure message handler", () => { it("skips triggering the delayed closure of the inline menu if a field is currently focused", async () => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 58e462943bf..3d2b1ec783c 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -70,6 +70,7 @@ import { generateDomainMatchPatterns, generateRandomChars, isInvalidResponseStatusCode, + rectHasSize, specialCharacterToKeyMap, } from "../utils"; @@ -130,6 +131,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private currentInlineMenuCiphersCount: number = 0; private currentAddNewItemData: CurrentAddNewItemData; private focusedFieldData: FocusedFieldData; + private allFieldData: AutofillField[]; private isFieldCurrentlyFocused: boolean = false; private isFieldCurrentlyFilling: boolean = false; private isInlineMenuButtonVisible: boolean = false; @@ -1367,6 +1369,71 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.isInlineMenuListVisible = false; } + /** + * Get all the totp fields for the tab and frame of the currently focused field + */ + private getTotpFields(): AutofillField[] { + const currentTabId = this.focusedFieldData?.tabId; + const currentFrameId = this.focusedFieldData?.frameId; + const pageDetailsMap = this.pageDetailsForTab[currentTabId]; + const pageDetails = pageDetailsMap?.get(currentFrameId); + + const fields = pageDetails.details.fields; + const totpFields = fields.filter((f) => + this.inlineMenuFieldQualificationService.isTotpField(f), + ); + + return totpFields; + } + + /** + * calculates the postion and width for multi-input totp field inline menu + * @param totpFieldArray - the totp fields used to evaluate the position of the menu + */ + private calculateTotpMultiInputMenuBounds(totpFieldArray: AutofillField[]) { + // Filter the fields based on the provided totpfields + const filteredObjects = this.allFieldData.filter((obj) => + totpFieldArray.some((o) => o.opid === obj.opid), + ); + + // Return null if no matching objects are found + if (filteredObjects.length === 0) { + return null; + } + // Calculate the smallest left and largest right values to determine width + const left = Math.min( + ...filteredObjects.filter((obj) => rectHasSize(obj.rect)).map((obj) => obj.rect.left), + ); + const largestRight = Math.max( + ...filteredObjects.filter((obj) => rectHasSize(obj.rect)).map((obj) => obj.rect.right), + ); + + const width = largestRight - left; + + return { left, width }; + } + + /** + * calculates the postion for multi-input totp field inline button + * @param totpFieldArray - the totp fields used to evaluate the position of the menu + */ + private calculateTotpMultiInputButtonBounds(totpFieldArray: AutofillField[]) { + const filteredObjects = this.allFieldData.filter((obj) => + totpFieldArray.some((o) => o.opid === obj.opid), + ); + + if (filteredObjects.length === 0) { + return null; + } + + const maxRight = Math.max(...filteredObjects.map((obj) => obj.rect.right)); + const maxObject = filteredObjects.find((obj) => obj.rect.right === maxRight); + const top = maxObject.rect.top - maxObject.rect.height * 0.39; + const left = maxRight - maxObject.rect.height * 0.3; + + return { left, top }; + } + /** * Updates the position of either the inline menu list or button. The position * is based on the focused field's position and dimensions. @@ -1472,8 +1539,17 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameTopOffset = subFrameOffsets?.top || 0; const subFrameLeftOffset = subFrameOffsets?.left || 0; - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; + const { width, height } = this.focusedFieldData.focusedFieldRects; + let { top, left } = this.focusedFieldData.focusedFieldRects; const { paddingRight, paddingLeft } = this.focusedFieldData.focusedFieldStyles; + + if (this.isTotpFieldForCurrentField()) { + const totpFields = this.getTotpFields(); + if (totpFields.length > 1) { + ({ left, top } = this.calculateTotpMultiInputButtonBounds(totpFields)); + } + } + let elementOffset = height * 0.37; if (height >= 35) { elementOffset = height >= 50 ? height * 0.47 : height * 0.42; @@ -1512,7 +1588,16 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameTopOffset = subFrameOffsets?.top || 0; const subFrameLeftOffset = subFrameOffsets?.left || 0; - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; + const { top, height } = this.focusedFieldData.focusedFieldRects; + let { left, width } = this.focusedFieldData.focusedFieldRects; + + if (this.isTotpFieldForCurrentField()) { + const totpFields = this.getTotpFields(); + + if (totpFields.length > 1) { + ({ left, width } = this.calculateTotpMultiInputMenuBounds(totpFields)); + } + } this.inlineMenuPosition.list = { top: Math.round(top + height + subFrameTopOffset), @@ -1535,7 +1620,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param sender - The sender of the extension message */ private setFocusedFieldData( - { focusedFieldData }: OverlayBackgroundExtensionMessage, + { focusedFieldData, allFieldsRect }: OverlayBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, ) { if ( @@ -1552,6 +1637,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const previousFocusedFieldData = this.focusedFieldData; this.focusedFieldData = { ...focusedFieldData, tabId: sender.tab.id, frameId: sender.frameId }; + this.allFieldData = allFieldsRect; this.isFieldCurrentlyFocused = true; if (this.shouldUpdatePasswordGeneratorMenuOnFieldFocus()) { diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index 7660b4ce5f0..c0be60f1cd0 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -1,3 +1,4 @@ +import { FieldRect } from "../background/abstractions/overlay.background"; // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { AutofillFieldQualifierType } from "../enums/autofill-field.enums"; @@ -124,4 +125,9 @@ export default class AutofillField { fieldQualifier?: AutofillFieldQualifierType; accountCreationFieldType?: InlineMenuAccountCreationFieldTypes; + + /** + * used for totp multiline calculations + */ + fieldRect?: FieldRect; } diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index daa65d74ae6..2db08db1872 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -957,8 +957,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ accountCreationFieldType: autofillFieldData?.accountCreationFieldType, }; + const allFields = this.formFieldElements; + const allFieldsRect = []; + + for (const key of allFields.keys()) { + const rect = await this.getMostRecentlyFocusedFieldRects(key); + allFieldsRect.push({ ...allFields.get(key), rect }); // Add the combined result to the array + } + await this.sendExtensionMessage("updateFocusedFieldData", { focusedFieldData: this.focusedFieldData, + allFieldsRect, }); } diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 12d26914d82..0e102dcfd99 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { FieldRect } from "../background/abstractions/overlay.background"; import { AutofillPort } from "../enums/autofill-port.enum"; import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types"; @@ -545,6 +546,17 @@ export const specialCharacterToKeyMap: Record = { "/": "forwardSlashCharacterDescriptor", }; +/** + * Determines if the current rect values are not all 0. + */ +export function rectHasSize(rect: FieldRect): boolean { + if (rect.right > 0 && rect.left > 0 && rect.top > 0 && rect.bottom > 0) { + return true; + } + + return false; +} + /** * Checks if all the values corresponding to the specified keys in an object are null. * If no keys are specified, checks all keys in the object. From 8a0ebd98af368922e98cd4f2bc18b8338f3ed073 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:36:56 -0800 Subject: [PATCH 166/270] align default globe vault icon (#12786) --- libs/angular/src/vault/components/icon.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/angular/src/vault/components/icon.component.html b/libs/angular/src/vault/components/icon.component.html index 9fec22f4a64..f16545617c9 100644 --- a/libs/angular/src/vault/components/icon.component.html +++ b/libs/angular/src/vault/components/icon.component.html @@ -10,7 +10,7 @@ loading="lazy" /> From b95080be21cd06d58239352bc891ea9f396452d7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:04:43 -0800 Subject: [PATCH 167/270] [PM-16874] [deps] Platform: Update electron to v33.3.1 (#12746) * [deps] Platform: Update electron to v33.3.1 * Update electron-builder electronVersion --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Hinton --- apps/desktop/electron-builder.json | 2 +- package-lock.json | 14 +++++++------- package.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index c8114d947e4..4302f302473 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -20,7 +20,7 @@ "**/node_modules/@bitwarden/desktop-napi/index.js", "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node" ], - "electronVersion": "33.2.1", + "electronVersion": "33.3.1", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/package-lock.json b/package-lock.json index d6f6b9d03a6..3d6ff011724 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,7 +133,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", - "electron": "33.2.1", + "electron": "33.3.1", "electron-builder": "24.13.3", "electron-log": "5.2.4", "electron-reload": "2.0.0-alpha.1", @@ -15318,9 +15318,9 @@ } }, "node_modules/electron": { - "version": "33.2.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-33.2.1.tgz", - "integrity": "sha512-SG/nmSsK9Qg1p6wAW+ZfqU+AV8cmXMTIklUL18NnOKfZLlum4ZsDoVdmmmlL39ZmeCaq27dr7CgslRPahfoVJg==", + "version": "33.3.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.3.1.tgz", + "integrity": "sha512-Z7l2bVgpdKxHQMI4i0CirBX2n+iCYKOx5mbzNM3BpOyFELwlobEXKmzCmEnwP+3EcNeIhUQyIEBFQxN06QgdIw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -15559,9 +15559,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "20.17.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.8.tgz", - "integrity": "sha512-ahz2g6/oqbKalW9sPv6L2iRbhLnojxjYWspAqhjvqSWBgGebEJT5GvRmk0QXPj3sbC6rU0GTQjPLQkmR8CObvA==", + "version": "20.17.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz", + "integrity": "sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ef55a938831..e6c476d2dfd 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", - "electron": "33.2.1", + "electron": "33.3.1", "electron-builder": "24.13.3", "electron-log": "5.2.4", "electron-reload": "2.0.0-alpha.1", From ff5043f9ffa8f533a0587093259f3df270bc7397 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Tue, 14 Jan 2025 02:32:32 -0500 Subject: [PATCH 168/270] Updated SonarQube GitHub action to v4.2.1 (#12836) --- .github/workflows/scan.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index b0874b38cbf..a09e8137b65 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -66,10 +66,9 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@02ef91109b2d589e757aefcfb2854c2783fd7b19 # v4.0.0 + uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: args: > -Dsonar.organization=${{ github.repository_owner }} From f6ec741a4a1a16873965fe9a7f5f56ffa6d266ee Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:20:29 +0100 Subject: [PATCH 169/270] remove the toggle for restarting cancel sub (#12842) Co-authored-by: Bernd Schoolmann --- .../app/billing/organizations/change-plan-dialog.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 902cac9c771..89c0075ba0d 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -30,7 +30,7 @@ }} -
    +
    Date: Tue, 14 Jan 2025 09:33:37 -0500 Subject: [PATCH 170/270] group client feature flags (#12849) --- libs/common/src/enums/feature-flag.enum.ts | 40 ++++++++++++---------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 0ab7d47acfc..feffe2ca442 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -4,6 +4,17 @@ * Flags MUST be short lived and SHALL be removed once enabled. */ export enum FeatureFlag { + /* Autofill */ + BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", + DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", + EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", + GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", + InlineMenuFieldQualification = "inline-menu-field-qualification", + InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", + InlineMenuTotp = "inline-menu-totp", + NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", + UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", + BrowserFilelessImport = "browser-fileless-import", ItemShare = "item-share", GeneratorToolsModernization = "generator-tools-modernization", @@ -11,23 +22,15 @@ export enum FeatureFlag { ExtensionRefresh = "extension-refresh", PersistPopupView = "persist-popup-view", PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", - UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", EmailVerification = "email-verification", - InlineMenuFieldQualification = "inline-menu-field-qualification", TwoFactorComponentRefactor = "two-factor-component-refactor", - InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", VaultBulkManagementAction = "vault-bulk-management-action", IdpAutoSubmitLogin = "idp-auto-submit-login", UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", - GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", - EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", - DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", AccountDeprovisioning = "pm-10308-account-deprovisioning", SSHKeyVaultItem = "ssh-key-vault-item", SSHAgent = "ssh-agent", - NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", - BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api", CipherKeyEncryption = "cipher-key-encryption", VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", @@ -39,7 +42,6 @@ export enum FeatureFlag { NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", - InlineMenuTotp = "inline-menu-totp", MacOsNativeCredentialSync = "macos-native-credential-sync", PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", @@ -59,6 +61,17 @@ const FALSE = false as boolean; * We support true as a value as we prefer flags to "enable" not "disable". */ export const DefaultFeatureFlagValue = { + /* Autofill */ + [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, + [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, + [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, + [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, + [FeatureFlag.InlineMenuFieldQualification]: FALSE, + [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, + [FeatureFlag.InlineMenuTotp]: FALSE, + [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, + [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, + [FeatureFlag.BrowserFilelessImport]: FALSE, [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.GeneratorToolsModernization]: FALSE, @@ -66,23 +79,15 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.ExtensionRefresh]: FALSE, [FeatureFlag.PersistPopupView]: FALSE, [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, - [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.EmailVerification]: FALSE, - [FeatureFlag.InlineMenuFieldQualification]: FALSE, [FeatureFlag.TwoFactorComponentRefactor]: FALSE, - [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, - [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, - [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, - [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, [FeatureFlag.AccountDeprovisioning]: FALSE, [FeatureFlag.SSHKeyVaultItem]: FALSE, [FeatureFlag.SSHAgent]: FALSE, - [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, - [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, [FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, @@ -94,7 +99,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, - [FeatureFlag.InlineMenuTotp]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, [FeatureFlag.PM12443RemovePagingLogic]: FALSE, From b083a05d9f19ec349d02d06b66c16a9c193984d1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 14 Jan 2025 15:47:19 +0100 Subject: [PATCH 171/270] Fix error prompt (#12830) --- libs/angular/src/auth/components/two-factor.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index 18bfe546600..e2b41ad086d 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -157,7 +157,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), - message: error, + message: this.i18nService.t("webauthnCancelOrTimeout"), }); }, (info: string) => { From 8717d79d51ca5429ca336a9944afa7becb62af6c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 14 Jan 2025 15:50:54 +0100 Subject: [PATCH 172/270] [PM-16918] Fix agent setting not disabling key usage (#12857) * Fix agent setting not disabling key usage * Cleanup * Change firstvaluefrom to withlatestfrom * Switch back to concatmap --- apps/desktop/src/platform/services/ssh-agent.service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/desktop/src/platform/services/ssh-agent.service.ts b/apps/desktop/src/platform/services/ssh-agent.service.ts index 726d28022e5..d4c7c5f460e 100644 --- a/apps/desktop/src/platform/services/ssh-agent.service.ts +++ b/apps/desktop/src/platform/services/ssh-agent.service.ts @@ -83,6 +83,15 @@ export class SshAgentService implements OnDestroy { this.messageListener .messages$(new CommandDefinition("sshagent.signrequest")) .pipe( + withLatestFrom(this.desktopSettingsService.sshAgentEnabled$), + concatMap(async ([message, enabled]) => { + if (!enabled) { + await ipc.platform.sshAgent.signRequestResponse(message.requestId as number, false); + } + return { message, enabled }; + }), + filter(({ enabled }) => enabled), + map(({ message }) => message), withLatestFrom(this.authService.activeAccountStatus$), // This switchMap handles unlocking the vault if it is locked: // - If the vault is locked, we will wait for it to be unlocked. From fbb1211a7baa3138d16efc5f122f2941b0c5d4a1 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 14 Jan 2025 16:11:37 +0100 Subject: [PATCH 173/270] [PM-17029] Convert libs/common to relative imports (#12852) Convert absolute paths in lib/common to relative. --- .../org-domain-api.service.abstraction.ts | 3 +-- ...rganization-domain-sso-details.response.ts | 2 +- .../organization-api.service.abstraction.ts | 3 +-- .../org-domain-api.service.spec.ts | 5 ++-- .../organization/organization-api.service.ts | 2 +- .../organization/vnext-organization.state.ts | 3 +-- .../webauthn-rotate-credential.request.ts | 2 +- .../devices/devices.service.implementation.ts | 3 +-- .../master-password.service.ts | 3 +-- ...-enrollment.service.implementation.spec.ts | 6 ++--- ...reset-enrollment.service.implementation.ts | 2 +- .../services/domain-settings.service.spec.ts | 3 +-- .../services/domain-settings.service.ts | 3 +-- libs/common/src/autofill/utils.spec.ts | 9 +++---- libs/common/src/autofill/utils.ts | 5 ++-- ...account-billing-api.service.abstraction.ts | 2 +- .../billing-api.service.abstraction.ts | 14 +++++----- .../organization-billing.service.ts | 4 +-- ...ization-billing-api.service.abstraction.ts | 2 +- .../abstractions/tax.service.abstraction.ts | 8 +++--- .../billing/models/domain/tax-information.ts | 2 +- .../expanded-tax-info-update.request.ts | 2 +- .../preview-organization-invoice.request.ts | 2 +- .../tokenized-payment-source.request.ts | 2 +- .../request/update-payment-method.request.ts | 4 +-- .../models/response/invoices.response.ts | 2 +- .../response/payment-source.response.ts | 4 +-- .../response/preview-invoice.response.ts | 2 +- .../provider-subscription-response.ts | 10 +++---- .../subscription-suspension.response.ts | 2 +- .../models/response/tax-id-types.response.ts | 2 +- ...ling-account-profile-state.service.spec.ts | 7 +++-- .../billing-account-profile-state.service.ts | 5 ++-- .../billing/services/billing-api.service.ts | 26 +++++++++---------- .../services/organization-billing.service.ts | 26 +++++++++---------- .../src/billing/services/tax.service.ts | 12 ++++----- .../default-process-reload.service.ts | 4 +-- .../src/models/export/ssh-key.export.ts | 2 +- libs/common/src/models/export/utils.ts | 2 +- .../vault-timeout-settings.service.spec.ts | 4 +-- .../vault-timeout.service.spec.ts | 4 +-- .../vault-timeout/vault-timeout.service.ts | 4 +-- ...-service-legacy-encryptor-provider.spec.ts | 10 +++---- .../key-service-legacy-encryptor-provider.ts | 4 +-- .../organization-encryptor.abstraction.ts | 3 +-- .../organization-key-encryptor.ts | 3 +-- .../user-encryptor.abstraction.ts | 3 +-- .../tools/cryptography/user-key-encryptor.ts | 3 +-- libs/common/src/tools/dependencies.ts | 4 +-- .../integration/integration-context.spec.ts | 2 +- .../tools/integration/integration-context.ts | 4 +-- .../tools/integration/rpc/rest-client.spec.ts | 4 +-- .../src/tools/integration/rpc/rest-client.ts | 4 +-- libs/common/src/tools/private-classifier.ts | 2 +- libs/common/src/tools/public-classifier.ts | 2 +- .../src/tools/send/models/domain/send.spec.ts | 4 +-- .../tools/send/services/send.service.spec.ts | 4 +-- .../tools/state/user-state-subject.spec.ts | 5 ++-- .../src/tools/state/user-state-subject.ts | 7 +++-- .../src/vault/abstractions/cipher.service.ts | 2 +- .../folder/folder-api.service.abstraction.ts | 2 +- .../src/vault/models/domain/cipher.spec.ts | 3 +-- .../common/src/vault/models/domain/ssh-key.ts | 3 +-- .../cipher-authorization.service.spec.ts | 6 ++--- .../services/cipher-authorization.service.ts | 4 +-- .../src/vault/services/cipher.service.spec.ts | 3 +-- .../src/vault/services/cipher.service.ts | 7 +++-- .../services/folder/folder-api.service.ts | 3 +-- .../vault/services/folder/folder.service.ts | 5 ++-- libs/common/tsconfig.json | 1 + 70 files changed, 148 insertions(+), 173 deletions(-) diff --git a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts index 5ecfca5ab84..5a393ed1996 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts @@ -1,7 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - +import { ListResponse } from "../../../models/response/list.response"; import { OrganizationDomainRequest } from "../../services/organization-domain/requests/organization-domain.request"; import { OrganizationDomainSsoDetailsResponse } from "./responses/organization-domain-sso-details.response"; diff --git a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts index c4817306a63..066d2d381e7 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../../models/response/base.response"; export class VerifiedOrganizationDomainSsoDetailsResponse extends BaseResponse { organizationName: string; diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 0c45c919e95..000d1655416 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -1,7 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; - import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request"; import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; @@ -13,6 +11,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; diff --git a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts index 7497a77e6f2..1052fe504d4 100644 --- a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts +++ b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts @@ -1,14 +1,13 @@ import { mock } from "jest-mock-extended"; import { lastValueFrom } from "rxjs"; -import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - import { ApiService } from "../../../abstractions/api.service"; +import { ListResponse } from "../../../models/response/list.response"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { OrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { OrganizationDomainResponse } from "../../abstractions/organization-domain/responses/organization-domain.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { OrgDomainApiService } from "./org-domain-api.service"; import { OrgDomainService } from "./org-domain.service"; diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index b3fd11982b8..598bb2a29db 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -1,6 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; import { ApiService } from "../../../abstractions/api.service"; import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; @@ -14,6 +13,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; diff --git a/libs/common/src/admin-console/services/organization/vnext-organization.state.ts b/libs/common/src/admin-console/services/organization/vnext-organization.state.ts index 48e09d6d076..fea0423f389 100644 --- a/libs/common/src/admin-console/services/organization/vnext-organization.state.ts +++ b/libs/common/src/admin-console/services/organization/vnext-organization.state.ts @@ -1,7 +1,6 @@ import { Jsonify } from "type-fest"; -import { ORGANIZATIONS_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; - +import { ORGANIZATIONS_DISK, UserKeyDefinition } from "../../../platform/state"; import { OrganizationData } from "../../models/data/organization.data"; /** diff --git a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts index 588f75bde49..84103fe5d29 100644 --- a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts +++ b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts @@ -1,10 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { RotateableKeySet } from "../../../../../auth/src/common/models"; +import { EncString } from "../../../platform/models/domain/enc-string"; export class WebauthnRotateCredentialRequest { id: string; diff --git a/libs/common/src/auth/services/devices/devices.service.implementation.ts b/libs/common/src/auth/services/devices/devices.service.implementation.ts index cd6f1148dd8..cdaa7a9fc4e 100644 --- a/libs/common/src/auth/services/devices/devices.service.implementation.ts +++ b/libs/common/src/auth/services/devices/devices.service.implementation.ts @@ -1,8 +1,7 @@ import { Observable, defer, map } from "rxjs"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; - import { ListResponse } from "../../../models/response/list.response"; +import { AppIdService } from "../../../platform/abstractions/app-id.service"; import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction"; import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { DeviceView } from "../../abstractions/devices/views/device.view"; diff --git a/libs/common/src/auth/services/master-password/master-password.service.ts b/libs/common/src/auth/services/master-password/master-password.service.ts index 3ac00adf8e5..14e7522a836 100644 --- a/libs/common/src/auth/services/master-password/master-password.service.ts +++ b/libs/common/src/auth/services/master-password/master-password.service.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; - import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; +import { LogService } from "../../../platform/abstractions/log.service"; import { StateService } from "../../../platform/abstractions/state.service"; import { EncryptionType } from "../../../platform/enums"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index 95b6f3f8b9d..ddd24ae7907 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -2,17 +2,15 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { UserId } from "../../../../common/src/types/guid"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; +import { UserId } from "../../types/guid"; import { Account, AccountInfo, AccountService } from "../abstractions/account.service"; import { PasswordResetEnrollmentServiceImplementation } from "./password-reset-enrollment.service.implementation"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index 0bdf98cef7b..22d5384e6ac 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -6,12 +6,12 @@ import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { Utils } from "../../platform/misc/utils"; import { UserKey } from "../../types/key"; diff --git a/libs/common/src/autofill/services/domain-settings.service.spec.ts b/libs/common/src/autofill/services/domain-settings.service.spec.ts index 5c5f85e7e67..36f7d0eacec 100644 --- a/libs/common/src/autofill/services/domain-settings.service.spec.ts +++ b/libs/common/src/autofill/services/domain-settings.service.spec.ts @@ -1,9 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/autofill/services/domain-settings.service.ts b/libs/common/src/autofill/services/domain-settings.service.ts index 7d25c4e25d3..b2833b9ee25 100644 --- a/libs/common/src/autofill/services/domain-settings.service.ts +++ b/libs/common/src/autofill/services/domain-settings.service.ts @@ -2,8 +2,7 @@ // @ts-strict-ignore import { map, Observable, switchMap, of } from "rxjs"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; - +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { NeverDomains, EquivalentDomains, diff --git a/libs/common/src/autofill/utils.spec.ts b/libs/common/src/autofill/utils.spec.ts index 4dd36ba7d89..554dc973b48 100644 --- a/libs/common/src/autofill/utils.spec.ts +++ b/libs/common/src/autofill/utils.spec.ts @@ -1,9 +1,6 @@ -import { - normalizeExpiryYearFormat, - isCardExpired, - parseYearMonthExpiry, -} from "@bitwarden/common/autofill/utils"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { CardView } from "../vault/models/view/card.view"; + +import { normalizeExpiryYearFormat, isCardExpired, parseYearMonthExpiry } from "./utils"; function getExpiryYearValueFormats(currentCentury: string) { return [ diff --git a/libs/common/src/autofill/utils.ts b/libs/common/src/autofill/utils.ts index 6bee5e1a198..a77ea8a715d 100644 --- a/libs/common/src/autofill/utils.ts +++ b/libs/common/src/autofill/utils.ts @@ -1,11 +1,12 @@ +import { CardView } from "../vault/models/view/card.view"; + import { DelimiterPatternExpression, ExpiryFullYearPattern, ExpiryFullYearPatternExpression, IrrelevantExpiryCharactersPatternExpression, MonthPatternExpression, -} from "@bitwarden/common/autofill/constants"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +} from "./constants"; type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; diff --git a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts index 06400081e11..e0e8b7377c5 100644 --- a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts @@ -3,7 +3,7 @@ import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class AccountBillingApiServiceAbstraction { getBillingInvoices: (status?: string, startAfter?: string) => Promise; diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index 4b08b52a136..928f65a3636 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -1,20 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export abstract class BillingApiServiceAbstraction { diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index 7c4e0a39f8f..1e68488ac98 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -1,11 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response"; -import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; import { InitiationPath } from "../../models/request/reference-event.request"; import { PaymentMethodType, PlanType } from "../enums"; +import { BillingSourceResponse } from "../models/response/billing.response"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; export type OrganizationInformation = { name: string; diff --git a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts index 6a72724a5ec..2ed25491049 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts @@ -3,7 +3,7 @@ import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class OrganizationBillingApiServiceAbstraction { getBillingInvoices: ( diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts index 438d3f394e0..7a744dae856 100644 --- a/libs/common/src/billing/abstractions/tax.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/tax.service.abstraction.ts @@ -1,7 +1,7 @@ -import { CountryListItem } from "@bitwarden/common/billing/models/domain"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; export abstract class TaxServiceAbstraction { abstract getCountries(): CountryListItem[]; diff --git a/libs/common/src/billing/models/domain/tax-information.ts b/libs/common/src/billing/models/domain/tax-information.ts index 78e1bcc42b5..794cdef3ed4 100644 --- a/libs/common/src/billing/models/domain/tax-information.ts +++ b/libs/common/src/billing/models/domain/tax-information.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; +import { TaxInfoResponse } from "../response/tax-info.response"; export class TaxInformation { country: string; diff --git a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts index 784d2691629..83b254ac512 100644 --- a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts +++ b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TaxInformation } from "@bitwarden/common/billing/models/domain/tax-information"; +import { TaxInformation } from "../domain/tax-information"; import { TaxInfoUpdateRequest } from "./tax-info-update.request"; diff --git a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts index 365dff5c110..40d8db03d3b 100644 --- a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts +++ b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts @@ -1,4 +1,4 @@ -import { PlanType } from "@bitwarden/common/billing/enums"; +import { PlanType } from "../../enums"; export class PreviewOrganizationInvoiceRequest { organizationId?: string; diff --git a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts index c740e4157ed..e4bf575cc6a 100644 --- a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts +++ b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { PaymentMethodType } from "../../enums"; export class TokenizedPaymentSourceRequest { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/request/update-payment-method.request.ts b/libs/common/src/billing/models/request/update-payment-method.request.ts index 9ef91ae579b..10b03103716 100644 --- a/libs/common/src/billing/models/request/update-payment-method.request.ts +++ b/libs/common/src/billing/models/request/update-payment-method.request.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; +import { ExpandedTaxInfoUpdateRequest } from "./expanded-tax-info-update.request"; +import { TokenizedPaymentSourceRequest } from "./tokenized-payment-source.request"; export class UpdatePaymentMethodRequest { paymentSource: TokenizedPaymentSourceRequest; diff --git a/libs/common/src/billing/models/response/invoices.response.ts b/libs/common/src/billing/models/response/invoices.response.ts index bf797ba42d6..05c95b83c6f 100644 --- a/libs/common/src/billing/models/response/invoices.response.ts +++ b/libs/common/src/billing/models/response/invoices.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class InvoicesResponse extends BaseResponse { invoices: InvoiceResponse[] = []; diff --git a/libs/common/src/billing/models/response/payment-source.response.ts b/libs/common/src/billing/models/response/payment-source.response.ts index 1aeeb450b11..93418fc2f55 100644 --- a/libs/common/src/billing/models/response/payment-source.response.ts +++ b/libs/common/src/billing/models/response/payment-source.response.ts @@ -1,5 +1,5 @@ -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; +import { PaymentMethodType } from "../../enums"; export class PaymentSourceResponse extends BaseResponse { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/response/preview-invoice.response.ts b/libs/common/src/billing/models/response/preview-invoice.response.ts index c822a569bb3..efd3da3e9f1 100644 --- a/libs/common/src/billing/models/response/preview-invoice.response.ts +++ b/libs/common/src/billing/models/response/preview-invoice.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class PreviewInvoiceResponse extends BaseResponse { effectiveTaxRate: number; diff --git a/libs/common/src/billing/models/response/provider-subscription-response.ts b/libs/common/src/billing/models/response/provider-subscription-response.ts index 2ecf988addd..4481f7588ff 100644 --- a/libs/common/src/billing/models/response/provider-subscription-response.ts +++ b/libs/common/src/billing/models/response/provider-subscription-response.ts @@ -1,9 +1,9 @@ -import { ProviderType } from "@bitwarden/common/admin-console/enums"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { SubscriptionSuspensionResponse } from "@bitwarden/common/billing/models/response/subscription-suspension.response"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; - +import { ProviderType } from "../../../admin-console/enums"; import { BaseResponse } from "../../../models/response/base.response"; +import { PlanType, ProductTierType } from "../../enums"; + +import { SubscriptionSuspensionResponse } from "./subscription-suspension.response"; +import { TaxInfoResponse } from "./tax-info.response"; export class ProviderSubscriptionResponse extends BaseResponse { status: string; diff --git a/libs/common/src/billing/models/response/subscription-suspension.response.ts b/libs/common/src/billing/models/response/subscription-suspension.response.ts index 418e1c443c8..3d714a05dba 100644 --- a/libs/common/src/billing/models/response/subscription-suspension.response.ts +++ b/libs/common/src/billing/models/response/subscription-suspension.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class SubscriptionSuspensionResponse extends BaseResponse { suspensionDate: string; diff --git a/libs/common/src/billing/models/response/tax-id-types.response.ts b/libs/common/src/billing/models/response/tax-id-types.response.ts index 0d5cce46c8c..f31f2133b34 100644 --- a/libs/common/src/billing/models/response/tax-id-types.response.ts +++ b/libs/common/src/billing/models/response/tax-id-types.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class TaxIdTypesResponse extends BaseResponse { taxIdTypes: TaxIdTypeResponse[] = []; diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts index 372d8099865..02dbef469d6 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts @@ -1,17 +1,16 @@ import { firstValueFrom } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - import { FakeAccountService, mockAccountServiceWith, FakeStateProvider, FakeSingleUserState, } from "../../../../spec"; +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { UserId } from "../../../types/guid"; import { BillingAccountProfile } from "../../abstractions/account/billing-account-profile-state.service"; +import { BillingHistoryResponse } from "../../models/response/billing-history.response"; import { BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts index 579a81eeb5c..155ce1493b4 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts @@ -1,8 +1,7 @@ import { map, Observable, combineLatest, concatMap } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { BILLING_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 7ce5602f3cc..4306324395e 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -1,25 +1,25 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ToastService } from "@bitwarden/components"; import { ApiService } from "../../abstractions/api.service"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; -import { BillingApiServiceAbstraction } from "../../billing/abstractions"; -import { PaymentMethodType } from "../../billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request"; -import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; -import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; -import { PlanResponse } from "../../billing/models/response/plan.response"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; +import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; +import { LogService } from "../../platform/abstractions/log.service"; +import { BillingApiServiceAbstraction } from "../abstractions"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; +import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; +import { PlanResponse } from "../models/response/plan.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export class BillingApiService implements BillingApiServiceAbstraction { diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index ca10b368662..da1a1666ff0 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -1,18 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { - BillingApiServiceAbstraction, - OrganizationBillingServiceAbstraction, - OrganizationInformation, - PaymentInformation, - PlanInformation, - SubscriptionInformation, -} from "@bitwarden/common/billing/abstractions"; -import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.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 { SyncService } from "@bitwarden/common/platform/sync"; import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; @@ -20,12 +7,25 @@ import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../ import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { SyncService } from "../../platform/sync"; import { OrgKey } from "../../types/key"; +import { + BillingApiServiceAbstraction, + OrganizationBillingServiceAbstraction, + OrganizationInformation, + PaymentInformation, + PlanInformation, + SubscriptionInformation, +} from "../abstractions"; import { PlanType } from "../enums"; import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request"; +import { BillingSourceResponse } from "../models/response/billing.response"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; interface OrganizationKeys { encryptedKey: EncString; diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts index 45e57267ec0..aa27c99adc8 100644 --- a/libs/common/src/billing/services/tax.service.ts +++ b/libs/common/src/billing/services/tax.service.ts @@ -1,9 +1,9 @@ -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { CountryListItem } from "@bitwarden/common/billing/models/domain"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; +import { ApiService } from "../../abstractions/api.service"; +import { TaxServiceAbstraction } from "../abstractions/tax.service.abstraction"; +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; export class TaxService implements TaxServiceAbstraction { constructor(private apiService: ApiService) {} diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index f1c3aed6a58..3f099b77328 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -2,8 +2,6 @@ // @ts-strict-ignore import { firstValueFrom, map, timeout } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { BiometricStateService } from "@bitwarden/key-management"; // FIXME: remove `src` and fix import @@ -14,6 +12,8 @@ import { AccountService } from "../../auth/abstractions/account.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; +import { MessagingService } from "../../platform/abstractions/messaging.service"; import { UserId } from "../../types/guid"; import { ProcessReloadServiceAbstraction } from "../abstractions/process-reload.service"; diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts index c502ddafd47..a99ebac34b3 100644 --- a/libs/common/src/models/export/ssh-key.export.ts +++ b/libs/common/src/models/export/ssh-key.export.ts @@ -1,9 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { SshKeyView as SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { EncString } from "../../platform/models/domain/enc-string"; import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key"; +import { SshKeyView as SshKeyView } from "../../vault/models/view/ssh-key.view"; import { safeGetString } from "./utils"; diff --git a/libs/common/src/models/export/utils.ts b/libs/common/src/models/export/utils.ts index 630b4898503..b7e0b74b611 100644 --- a/libs/common/src/models/export/utils.ts +++ b/libs/common/src/models/export/utils.ts @@ -1,4 +1,4 @@ -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncString } from "../../platform/models/domain/enc-string"; export function safeGetString(value: string | EncString) { if (value == null) { diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts index 4168baf1383..77ed6c960ab 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts @@ -6,8 +6,6 @@ import { FakeUserDecryptionOptions as UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; import { BiometricStateService } from "@bitwarden/key-management"; // FIXME: remove `src` and fix import @@ -20,10 +18,12 @@ import { Policy } from "../../admin-console/models/domain/policy"; import { TokenService } from "../../auth/abstractions/token.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { LogService } from "../../platform/abstractions/log.service"; +import { Utils } from "../../platform/misc/utils"; import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION, } from "../../services/vault-timeout/vault-timeout-settings.state"; +import { UserId } from "../../types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts index 8a166e63a1f..986bbbf95a4 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts @@ -3,8 +3,6 @@ import { BehaviorSubject, from, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; import { BiometricsService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; @@ -15,10 +13,12 @@ import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { StateService } from "../../platform/abstractions/state.service"; import { Utils } from "../../platform/misc/utils"; +import { TaskSchedulerService } from "../../platform/scheduling"; import { StateEventRunnerService } from "../../platform/state"; import { UserId } from "../../types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index 8ab10b44b24..08dc02bb1ab 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -4,8 +4,6 @@ import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from " import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { BiometricsService } from "@bitwarden/key-management"; import { SearchService } from "../../abstractions/search.service"; @@ -16,9 +14,11 @@ import { AuthService } from "../../auth/abstractions/auth.service"; import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { TaskSchedulerService, ScheduledTaskNames } from "../../platform/scheduling"; import { StateEventRunnerService } from "../../platform/state"; import { UserId } from "../../types/guid"; import { CipherService } from "../../vault/abstractions/cipher.service"; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts index 12257905d1c..831cad74155 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -1,13 +1,13 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, Subject } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { CsprngArray } from "@bitwarden/common/types/csprng"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../../types/csprng"; +import { OrganizationId, UserId } from "../../types/guid"; +import { OrgKey, UserKey } from "../../types/key"; import { OrganizationBound, UserBound } from "../dependencies"; import { KeyServiceLegacyEncryptorProvider } from "./key-service-legacy-encryptor-provider"; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts index 3eee08eb6bd..d4a8dec7dc3 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts @@ -12,10 +12,10 @@ import { takeWhile, } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { KeyService } from "@bitwarden/key-management"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { OrganizationId, UserId } from "../../types/guid"; import { OrganizationBound, SingleOrganizationDependency, diff --git a/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts index dda8bfe957a..9eec207e92d 100644 --- a/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts +++ b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { OrganizationId } from "@bitwarden/common/types/guid"; - import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; /** An encryption strategy that protects a type's secrets with * organization-specific keys. This strategy is bound to a specific organization. diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.ts index 3fdc0a1da32..d3b7dae10f5 100644 --- a/libs/common/src/tools/cryptography/organization-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { OrganizationId } from "@bitwarden/common/types/guid"; - import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; import { OrgKey } from "../../types/key"; import { DataPacker } from "../state/data-packer.abstraction"; diff --git a/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts index d63a5e908ef..6bb0e252af3 100644 --- a/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts +++ b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; /** An encryption strategy that protects a type's secrets with * user-specific keys. This strategy is bound to a specific user. diff --git a/libs/common/src/tools/cryptography/user-key-encryptor.ts b/libs/common/src/tools/cryptography/user-key-encryptor.ts index 15a3a1b6d5c..296c33ea1dc 100644 --- a/libs/common/src/tools/cryptography/user-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; import { UserKey } from "../../types/key"; import { DataPacker } from "../state/data-packer.abstraction"; diff --git a/libs/common/src/tools/dependencies.ts b/libs/common/src/tools/dependencies.ts index cdae45bc94a..c22e71cff67 100644 --- a/libs/common/src/tools/dependencies.ts +++ b/libs/common/src/tools/dependencies.ts @@ -1,7 +1,7 @@ import { Observable } from "rxjs"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { Policy } from "../admin-console/models/domain/policy"; +import { OrganizationId, UserId } from "../types/guid"; import { OrganizationEncryptor } from "./cryptography/organization-encryptor.abstraction"; import { UserEncryptor } from "./cryptography/user-encryptor.abstraction"; diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts index 58115c783c7..42581c08dee 100644 --- a/libs/common/src/tools/integration/integration-context.spec.ts +++ b/libs/common/src/tools/integration/integration-context.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nService } from "../../platform/abstractions/i18n.service"; import { IntegrationContext } from "./integration-context"; import { IntegrationId } from "./integration-id"; diff --git a/libs/common/src/tools/integration/integration-context.ts b/libs/common/src/tools/integration/integration-context.ts index f30810ff81a..40648df6803 100644 --- a/libs/common/src/tools/integration/integration-context.ts +++ b/libs/common/src/tools/integration/integration-context.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { I18nService } from "../../platform/abstractions/i18n.service"; +import { Utils } from "../../platform/misc/utils"; import { IntegrationMetadata } from "./integration-metadata"; import { ApiSettings, IntegrationRequest } from "./rpc"; diff --git a/libs/common/src/tools/integration/rpc/rest-client.spec.ts b/libs/common/src/tools/integration/rpc/rest-client.spec.ts index e113ab9ff43..9b76d305e6f 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.spec.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { RestClient } from "./rest-client"; diff --git a/libs/common/src/tools/integration/rpc/rest-client.ts b/libs/common/src/tools/integration/rpc/rest-client.ts index 287bb4f2573..c42244166e7 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { JsonRpc } from "./rpc"; diff --git a/libs/common/src/tools/private-classifier.ts b/libs/common/src/tools/private-classifier.ts index de21f78f1c6..58244ae9906 100644 --- a/libs/common/src/tools/private-classifier.ts +++ b/libs/common/src/tools/private-classifier.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PrivateClassifier implements Classifier, Data> { constructor(private keys: (keyof Jsonify)[] = undefined) {} diff --git a/libs/common/src/tools/public-classifier.ts b/libs/common/src/tools/public-classifier.ts index e7c8c24ba78..e036ebd1c42 100644 --- a/libs/common/src/tools/public-classifier.ts +++ b/libs/common/src/tools/public-classifier.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PublicClassifier implements Classifier> { constructor(private keys: (keyof Jsonify)[]) {} diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index 408a4ea7172..fcc273d41bb 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,12 +1,12 @@ import { mock } from "jest-mock-extended"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; import { makeStaticByteArray, mockEnc } from "../../../../../spec"; import { EncryptService } from "../../../../platform/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../../platform/services/container.service"; +import { UserKey } from "../../../../types/key"; import { SendType } from "../../enums/send-type"; import { SendData } from "../data/send.data"; diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index ff814302513..662fee02bbf 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -1,8 +1,6 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { KeyService } from "@bitwarden/key-management"; import { @@ -13,12 +11,14 @@ import { mockAccountServiceWith, } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EnvironmentService } from "../../../platform/abstractions/environment.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; import { Utils } from "../../../platform/misc/utils"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../platform/services/container.service"; +import { SelfHostedEnvironment } from "../../../platform/services/default-environment.service"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { SendType } from "../enums/send-type"; diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts index 6a50a1dd668..8111f6f9f17 100644 --- a/libs/common/src/tools/state/user-state-subject.spec.ts +++ b/libs/common/src/tools/state/user-state-subject.spec.ts @@ -1,9 +1,8 @@ import { BehaviorSubject, of, Subject } from "rxjs"; -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - import { awaitAsync, FakeSingleUserState, ObservableTracker } from "../../../spec"; +import { GENERATOR_DISK, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { UserBound } from "../dependencies"; import { PrivateClassifier } from "../private-classifier"; diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts index d508cf71663..c00560304e4 100644 --- a/libs/common/src/tools/state/user-state-subject.ts +++ b/libs/common/src/tools/state/user-state-subject.ts @@ -26,10 +26,9 @@ import { skip, } from "rxjs"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SingleUserState, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - +import { EncString } from "../../platform/models/domain/enc-string"; +import { SingleUserState, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { UserBound } from "../dependencies"; import { anyComplete, errorOnChange, ready, withLatestReady } from "../rx"; diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 068b2fc52d9..88cd476606d 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -2,7 +2,6 @@ // @ts-strict-ignore import { Observable } from "rxjs"; -import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; @@ -11,6 +10,7 @@ import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid import { UserKey } from "../../types/key"; import { CipherType } from "../enums/cipher-type"; import { CipherData } from "../models/data/cipher.data"; +import { LocalData } from "../models/data/local.data"; import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherWithIdRequest } from "../models/request/cipher-with-id.request"; diff --git a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts index 859f2183edb..b95dd35e5ec 100644 --- a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { UserId } from "@bitwarden/common/types/guid"; +import { UserId } from "../../../types/guid"; import { Folder } from "../../models/domain/folder"; import { FolderResponse } from "../../models/response/folder.response"; diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 64df3204aca..dd79da3086e 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -1,8 +1,6 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; @@ -12,6 +10,7 @@ import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { EncString } from "../../../platform/models/domain/enc-string"; import { ContainerService } from "../../../platform/services/container.service"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; +import { UserId } from "../../../types/guid"; import { CipherService } from "../../abstractions/cipher.service"; import { FieldType, SecureNoteType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index 9ce16fe4557..b4df172e543 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; - import Domain from "../../../platform/models/domain/domain-base"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SshKeyData } from "../data/ssh-key.data"; import { SshKeyView } from "../view/ssh-key.view"; diff --git a/libs/common/src/vault/services/cipher-authorization.service.spec.ts b/libs/common/src/vault/services/cipher-authorization.service.spec.ts index cccd29ad697..15e8b03bfad 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.spec.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.spec.ts @@ -2,10 +2,10 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "../../admin-console/models/domain/organization"; +import { CollectionId } from "../../types/guid"; import { CipherView } from "../models/view/cipher.view"; import { diff --git a/libs/common/src/vault/services/cipher-authorization.service.ts b/libs/common/src/vault/services/cipher-authorization.service.ts index 260d1eeece7..025d6b1cdc3 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.ts @@ -3,9 +3,9 @@ import { map, Observable, of, shareReplay, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { CollectionId } from "../../types/guid"; import { Cipher } from "../models/domain/cipher"; import { CipherView } from "../models/view/cipher.view"; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 8ab6a2d3d34..0d6578f165d 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -1,8 +1,6 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, map, of } from "rxjs"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; - // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { @@ -17,6 +15,7 @@ import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; import { UriMatchStrategy } from "../../models/domain/domain-service"; +import { BulkEncryptService } from "../../platform/abstractions/bulk-encrypt.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 12712f169a3..fe946fbb064 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -14,21 +14,20 @@ import { } from "rxjs"; import { SemVer } from "semver"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; - // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; +import { AccountService } from "../../auth/abstractions/account.service"; import { AutofillSettingsServiceAbstraction } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; import { View } from "../../models/view/view"; +import { BulkEncryptService } from "../../platform/abstractions/bulk-encrypt.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; diff --git a/libs/common/src/vault/services/folder/folder-api.service.ts b/libs/common/src/vault/services/folder/folder-api.service.ts index 24831393668..9d434cc646d 100644 --- a/libs/common/src/vault/services/folder/folder-api.service.ts +++ b/libs/common/src/vault/services/folder/folder-api.service.ts @@ -1,6 +1,5 @@ -import { UserId } from "@bitwarden/common/types/guid"; - import { ApiService } from "../../../abstractions/api.service"; +import { UserId } from "../../../types/guid"; import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; import { FolderData } from "../../../vault/models/data/folder.data"; diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index fe77806877d..c21a92fd894 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - +import { EncryptService } from ".././../../platform/abstractions/encrypt.service"; +import { Utils } from ".././../../platform/misc/utils"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index 128511ee410..c28b60e28f8 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -4,6 +4,7 @@ "paths": { "@bitwarden/admin-console/common": ["../admin-console/src/common"], "@bitwarden/auth/common": ["../auth/src/common"], + // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], "@bitwarden/key-management": ["../key-management/src"], From 8e9502943977bc724429fbebaf2f27fa4f57aff5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 14 Jan 2025 16:20:18 +0100 Subject: [PATCH 174/270] [PM-16985] Fix biometrics not working in firefox or windows (#12833) * Fix biometrics not working in firefox or windows * Remove logs * Update badge after biometric unlock * Add removal todo note * Remove debug logging * Fix type warnings * Fix userkey typing in background biometrics service * Simplify types for userkey in foreground-browser-biometrics and runtime.background.ts --- .../settings/account-security.component.ts | 11 +++++- .../browser/src/background/main.background.ts | 12 ++++--- .../background-browser-biometrics.service.ts | 34 +++++++++++++++++-- .../foreground-browser-biometrics.ts | 5 ++- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 3b1727f89ac..1a64d860e45 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -507,7 +507,16 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { const biometricsPromise = async () => { try { - const result = await this.biometricsService.authenticateWithBiometrics(); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a.id)), + ); + let result = false; + try { + const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); + result = await this.keyService.validateUserKey(userKey, userId); + } catch (e) { + result = false; + } // prevent duplicate dialog biometricsResponseReceived = true; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 4bec3d6cc0a..98f3867e5ff 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -632,11 +632,6 @@ export default class MainBackground { this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); - this.biometricsService = new BackgroundBrowserBiometricsService( - runtimeNativeMessagingBackground, - this.logService, - ); - this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); this.pinService = new PinService( @@ -665,6 +660,13 @@ export default class MainBackground { this.kdfConfigService, ); + this.biometricsService = new BackgroundBrowserBiometricsService( + runtimeNativeMessagingBackground, + this.logService, + this.keyService, + this.biometricStateService, + ); + this.appIdService = new AppIdService(this.storageService, this.logService); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 8e6fc562d14..a0f54c58a64 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -1,9 +1,17 @@ import { Injectable } from "@angular/core"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; -import { BiometricsService, BiometricsCommands, BiometricsStatus } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsCommands, + BiometricsStatus, + KeyService, + BiometricStateService, +} from "@bitwarden/key-management"; import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; import { BrowserApi } from "../../platform/browser/browser-api"; @@ -13,6 +21,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { constructor( private nativeMessagingBackground: () => NativeMessagingBackground, private logService: LogService, + private keyService: KeyService, + private biometricStateService: BiometricStateService, ) { super(); } @@ -74,12 +84,20 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); + // todo remove after 2025.3 if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { const response = await this.nativeMessagingBackground().callCommand({ command: BiometricsCommands.Unlock, }); if (response.response == "unlocked") { - return response.userKeyB64; + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.biometricStateService.setFingerprintValidated(true); + this.keyService.setUserKey(userKey, userId); + return userKey; + } } else { return null; } @@ -89,7 +107,15 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: userId, }); if (response.response) { - return response.userKeyB64; + // In case the requesting foreground context dies (popup), the userkey should still be set, so the user is unlocked / the setting should be enabled + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.biometricStateService.setFingerprintValidated(true); + this.keyService.setUserKey(userKey, userId); + return userKey; + } } else { return null; } @@ -98,6 +124,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { this.logService.info("Biometric unlock for user failed", e); throw new Error("Biometric unlock failed"); } + + return null; } async getBiometricsStatusForUser(id: UserId): Promise { diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index 0235ad5bd9c..43cc25e4dad 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -1,4 +1,3 @@ -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { BiometricsCommands, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; @@ -29,13 +28,13 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { async unlockWithBiometricsForUser(userId: UserId): Promise { const response = await BrowserApi.sendMessageWithResponse<{ - result: string; + result: UserKey; error: string; }>(BiometricsCommands.UnlockWithBiometricsForUser, { userId }); if (!response.result) { return null; } - return SymmetricCryptoKey.fromString(response.result) as UserKey; + return response.result; } async getBiometricsStatusForUser(id: UserId): Promise { From e568a7a812d3dee9509f7797a25928f6ce2a6b34 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Tue, 14 Jan 2025 11:03:58 -0500 Subject: [PATCH 175/270] [CL-524] Ignore kitchen sink virtual scroll story (#12858) --- .../components/dialog-virtual-scroll-block.component.ts | 2 +- .../src/stories/kitchen-sink/kitchen-sink.stories.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index 02b49a3e915..7709506f050 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -12,7 +12,7 @@ import { TableDataSource, TableModule } from "../../../table"; imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], template: /*html*/ ` - + Id diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index 44080e29049..a90597c1710 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -196,4 +196,10 @@ export const VirtualScrollBlockingDialog: Story = { await userEvent.click(dialogButton); }, + parameters: { + chromatic: { + // TODO CL-524 fix flaky story (number of virtual scroll rows is inconsistent) + disableSnapshot: true, + }, + }, }; From 62e879940c439d40263c270371e58573cc4bcd04 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:23:59 -0500 Subject: [PATCH 176/270] [deps] SM: Update typescript-eslint monorepo to v8 (major) (#10601) * [deps] SM: Update typescript-eslint monorepo to v8 --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Hinton Co-authored-by: Daniel James Smith --- .eslintrc.json | 1 + apps/browser/postcss.config.js | 2 +- .../auto-submit-login.background.spec.ts | 4 + .../content/auto-submit-login.spec.ts | 2 + .../content/components/buttons/edit-button.ts | 2 + .../content/components/cipher/types.ts | 4 + .../content/content-message-handler.spec.ts | 2 + .../trigger-autofill-script-injection.spec.ts | 2 + ...trap-autofill-overlay-button.deprecated.ts | 2 + ...tstrap-autofill-overlay-list.deprecated.ts | 2 + .../content/fido2-content-script.spec.ts | 16 ++ .../fido2-page-script-append.mv2.spec.ts | 6 + .../fido2/content/fido2-page-script.ts | 2 + ...do2-page-script.webauthn-supported.spec.ts | 6 + ...2-page-script.webauthn-unsupported.spec.ts | 2 + apps/browser/src/autofill/notification/bar.ts | 2 + .../bootstrap-autofill-inline-menu-button.ts | 2 + .../bootstrap-autofill-inline-menu-list.ts | 2 + ...ootstrap-autofill-inline-menu-container.ts | 2 + .../autofill-overlay-content.service.ts | 2 + .../autofill/services/dom-query.service.ts | 2 + .../background/nativeMessaging.background.ts | 2 + .../background-browser-biometrics.service.ts | 4 + apps/browser/src/platform/flags.ts | 2 - .../offscreen-document.spec.ts | 2 + ...cal-backed-session-storage.service.spec.ts | 4 + .../local-backed-session-storage.service.ts | 2 + .../sdk/browser-sdk-client-factory.ts | 2 + apps/browser/src/popup/main.ts | 2 + .../content/lp-fileless-importer.spec.ts | 2 + ...-import-download-script-append.mv2.spec.ts | 2 + .../lp-suppress-import-download.spec.ts | 2 + .../admin-console/commands/share.command.ts | 2 + apps/cli/src/auth/commands/login.command.ts | 4 + apps/cli/src/commands/edit.command.ts | 2 + apps/cli/src/platform/flags.ts | 2 - .../node-env-secure-storage.service.ts | 4 + .../src/tools/send/commands/create.command.ts | 2 + .../src/tools/send/commands/edit.command.ts | 2 + .../tools/send/commands/receive.command.ts | 2 + apps/cli/src/vault/create.command.ts | 2 + apps/desktop/config/config.js | 2 + apps/desktop/postcss.config.js | 2 +- apps/desktop/scripts/after-pack.js | 2 +- apps/desktop/scripts/after-sign.js | 2 +- apps/desktop/scripts/build-macos-extension.js | 2 +- apps/desktop/scripts/start.js | 2 +- apps/desktop/sign.js | 2 +- apps/desktop/src/app/app-routing.module.ts | 1 + apps/desktop/src/app/main.ts | 2 + .../login/desktop-login-component.service.ts | 2 + .../src/auth/login/login-v1.component.ts | 2 + .../desktop/src/main/native-messaging.main.ts | 2 + apps/desktop/src/platform/flags.ts | 2 - .../biometric-message-handler.service.ts | 4 + .../duckduckgo-message-handler.service.ts | 4 + .../encrypted-message-handler.service.ts | 4 + apps/web/postcss.config.js | 2 +- .../common/base.events.component.ts | 2 + .../manage/entity-events.component.ts | 2 + ...families-for-enterprise-setup.component.ts | 2 + .../app/platform/web-sdk-client-factory.ts | 2 + .../tools/send/send-access-file.component.ts | 2 + .../folder-add-edit.component.ts | 2 + apps/web/src/connectors/captcha.ts | 6 + apps/web/src/connectors/duo-redirect.ts | 2 + apps/web/src/connectors/sso.ts | 2 + apps/web/src/connectors/webauthn-fallback.ts | 6 + apps/web/src/connectors/webauthn.ts | 6 + apps/web/src/utils/flags.ts | 2 - .../device-approvals.component.ts | 2 + .../services/sm-porting-api.service.ts | 2 + .../src/auth/components/login-v1.component.ts | 2 + .../vault/components/attachments.component.ts | 4 + .../src/vault/components/view.component.ts | 2 + .../login-decryption-options.component.ts | 2 + .../auth/src/angular/login/login.component.ts | 2 + .../decode-jwt-token-to-json.utility.ts | 4 + .../device-trust.service.implementation.ts | 2 + .../user-verification.service.ts | 4 + libs/common/src/platform/misc/flags.ts | 2 - libs/common/src/platform/misc/utils.ts | 4 + .../models/domain/domain-base.spec.ts | 4 + .../src/platform/models/domain/domain-base.ts | 2 +- .../src/platform/models/domain/enc-string.ts | 6 + ...ve-autofill-settings-to-state-providers.ts | 2 + ...ard-to-autofill-settings-state-provider.ts | 2 + ...move-domain-settings-to-state-providers.ts | 2 + .../src/tools/send/models/domain/send.ts | 2 + .../src/vault/icon/build-cipher-icon.ts | 2 + .../src/vault/models/domain/attachment.ts | 2 + .../src/vault/models/view/cipher.view.ts | 2 + .../src/vault/models/view/login-uri.view.ts | 2 + .../src/a11y/a11y-grid.directive.ts | 2 + .../src/navigation/nav-group.component.ts | 2 + .../src/services/import.service.spec.ts | 2 +- libs/key-management/src/key.service.ts | 2 + libs/shared/jest.config.angular.js | 2 +- ...edit-custom-field-dialog.component.spec.ts | 2 + .../download-attachment.component.ts | 2 + package-lock.json | 192 ++++++++++-------- package.json | 4 +- tsconfig.eslint.json | 1 + 103 files changed, 355 insertions(+), 106 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c10904c8670..3fd6dec3d7e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -56,6 +56,7 @@ "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], "@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }], + "@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }], "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], "no-console": "error", "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package. diff --git a/apps/browser/postcss.config.js b/apps/browser/postcss.config.js index c4513687e89..83e237f06e5 100644 --- a/apps/browser/postcss.config.js +++ b/apps/browser/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts index 73f936bb591..a300ac08660 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts @@ -453,12 +453,16 @@ describe("AutoSubmitLoginBackground", () => { sendMockExtensionMessage({ command: "triggerAutoSubmitLogin" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); it("skips acting on messages whose command does not have a registered handler", () => { sendMockExtensionMessage({ command: "someInvalidCommand" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); diff --git a/apps/browser/src/autofill/content/auto-submit-login.spec.ts b/apps/browser/src/autofill/content/auto-submit-login.spec.ts index ff1dbd4e945..d70fc1e7446 100644 --- a/apps/browser/src/autofill/content/auto-submit-login.spec.ts +++ b/apps/browser/src/autofill/content/auto-submit-login.spec.ts @@ -46,6 +46,8 @@ describe("AutoSubmitLogin content script", () => { beforeEach(() => { jest.useFakeTimers(); setupEnvironmentDefaults(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./auto-submit-login"); }); diff --git a/apps/browser/src/autofill/content/components/buttons/edit-button.ts b/apps/browser/src/autofill/content/components/buttons/edit-button.ts index 695cbfd3b9d..cacd2b59f0e 100644 --- a/apps/browser/src/autofill/content/components/buttons/edit-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts @@ -23,6 +23,8 @@ export function EditButton({ title=${buttonText} class=${editButtonStyles({ disabled, theme })} @click=${(event: Event) => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions !disabled && buttonAction(event); }} > diff --git a/apps/browser/src/autofill/content/components/cipher/types.ts b/apps/browser/src/autofill/content/components/cipher/types.ts index 24f528c5246..acdee756570 100644 --- a/apps/browser/src/autofill/content/components/cipher/types.ts +++ b/apps/browser/src/autofill/content/components/cipher/types.ts @@ -1,3 +1,5 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const CipherTypes = { Login: 1, SecureNote: 2, @@ -7,6 +9,8 @@ const CipherTypes = { type CipherType = (typeof CipherTypes)[keyof typeof CipherTypes]; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const CipherRepromptTypes = { None: 0, Password: 1, diff --git a/apps/browser/src/autofill/content/content-message-handler.spec.ts b/apps/browser/src/autofill/content/content-message-handler.spec.ts index 226fcb4bd61..a37a2e07678 100644 --- a/apps/browser/src/autofill/content/content-message-handler.spec.ts +++ b/apps/browser/src/autofill/content/content-message-handler.spec.ts @@ -19,6 +19,8 @@ describe("ContentMessageHandler", () => { ); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./content-message-handler"); }); diff --git a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts index 1ad985bc8e9..317f63e756c 100644 --- a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts +++ b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts @@ -6,6 +6,8 @@ describe("TriggerAutofillScriptInjection", () => { describe("init", () => { it("sends a message to the extension background", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../content/trigger-autofill-script-injection"); expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts index fde98a58a5f..fd6a79733cb 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayButton from "./autofill-overlay-button.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-button.scss"); (function () { diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts index 714ccbfbee5..5d587bd4293 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayList from "./autofill-overlay-list.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-list.scss"); (function () { diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts index 94bef354a79..8885ed6299c 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts @@ -60,6 +60,8 @@ describe("Fido2 Content Script", () => { chrome.runtime.connect = jest.fn(() => portSpy); it("destroys the messenger when the port is disconnected", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); triggerPortOnDisconnectEvent(portSpy); @@ -75,6 +77,8 @@ describe("Fido2 Content Script", () => { const mockResult = { credentialId: "mock" } as CreateCredentialResult; jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue(mockResult); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const response = await messenger.handler!(message, new AbortController()); @@ -99,6 +103,8 @@ describe("Fido2 Content Script", () => { data: mock(), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, new AbortController()); @@ -121,6 +127,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); const abortSpy = jest.spyOn(abortController.signal, "removeEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -141,6 +149,8 @@ describe("Fido2 Content Script", () => { abortController.abort(); }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -161,6 +171,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue({ error: errorMessage }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const result = messenger.handler!(message, abortController); @@ -175,6 +187,8 @@ describe("Fido2 Content Script", () => { contentType: "application/json", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); @@ -193,6 +207,8 @@ describe("Fido2 Content Script", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts index 6b9b41b5aac..69e17d26fe5 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts @@ -22,6 +22,8 @@ describe("FIDO2 page-script for manifest v2", () => { it("skips appending the `page-script.js` file if the document contentType is not `text/html`", () => { Object.defineProperty(window.document, "contentType", { value: "text/plain", writable: true }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).not.toHaveBeenCalled(); @@ -33,6 +35,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); @@ -48,6 +52,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index fd033090cd4..4c1761c37ba 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -267,6 +267,8 @@ import { Messenger } from "./messaging/messenger"; clearWaitForFocus(); void messenger.destroy(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { /** empty */ } diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index 31e8c941e86..f1aec69193b 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -55,6 +55,8 @@ describe("Fido2 page script with native WebAuthn support", () => { setupMockedWebAuthnSupport(); beforeAll(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); }); @@ -166,6 +168,8 @@ describe("Fido2 page script with native WebAuthn support", () => { contentType: "json/application", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); @@ -184,6 +188,8 @@ describe("Fido2 page script with native WebAuthn support", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts index e354453ca59..af1838ec942 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts @@ -50,6 +50,8 @@ describe("Fido2 page script without native WebAuthn support", () => { const mockCreateCredentialsResult = createCreateCredentialResultMock(); const mockCredentialRequestOptions = createCredentialRequestOptionsMock(); const mockCredentialAssertResult = createAssertCredentialResultMock(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); afterEach(() => { diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index a4b8ae44b6a..2c0ebe8e8e7 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -15,6 +15,8 @@ import { NotificationBarIframeInitData, } from "./abstractions/notification-bar"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./bar.scss"); const logService = new ConsoleLogService(false); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts index 0ed14a520c1..36ef3897c56 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuButton } from "./autofill-inline-menu-button"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./button.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts index c302c50b4a4..b46b208b084 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuList } from "./autofill-inline-menu-list"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./list.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts index 16d5c29d574..522b968e533 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts @@ -1,3 +1,5 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./menu-container.scss"); import { AutofillInlineMenuContainer } from "./autofill-inline-menu-container"; diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index 2db08db1872..acf0dedde27 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -1417,6 +1417,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ url.origin + pathWithoutTrailingSlashAndSearch, url.origin + pathWithoutTrailingSlashSearchAndHash, ]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_error) { return null; } diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index 0dbc246b235..16310397a03 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -235,6 +235,8 @@ export class DomQueryService implements DomQueryServiceInterface { if ((chrome as any).dom?.openOrClosedShadowRoot) { try { return (chrome as any).dom.openOrClosedShadowRoot(node); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index c134860f1a8..b08f1c8b566 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -342,6 +342,8 @@ export class NativeMessagingBackground { }; } this.port.postMessage(msg); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info( "[Native Messaging IPC] Disconnected from Bitwarden Desktop app because of the native port disconnecting.", diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index a0f54c58a64..4c4753c3f7f 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -75,6 +75,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { } } return BiometricsStatus.Available; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } @@ -142,6 +144,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: id, }) ).response; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } diff --git a/apps/browser/src/platform/flags.ts b/apps/browser/src/platform/flags.ts index 383e982f065..2b1040bcd8a 100644 --- a/apps/browser/src/platform/flags.ts +++ b/apps/browser/src/platform/flags.ts @@ -11,13 +11,11 @@ import { GroupPolicyEnvironment } from "../admin-console/types/group-policy-envi import { BrowserApi } from "./browser/browser-api"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = { accountSwitching?: boolean; } & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = { managedEnvironment?: GroupPolicyEnvironment; } & SharedDevFlags; diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts index 4065f2e46d7..67fa920d18d 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts @@ -8,6 +8,8 @@ describe("OffscreenDocument", () => { const browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); const consoleErrorSpy = jest.spyOn(console, "error"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../offscreen-document/offscreen-document"); describe("init", () => { diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts index 949ecebde8a..4d6a403a18a 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts @@ -48,6 +48,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( encrypted, sessionKey, @@ -69,6 +71,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( encrypted, sessionKey, diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.ts index bdc0bed80c0..900212ddefa 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.ts @@ -198,6 +198,8 @@ export class LocalBackedSessionStorageService private compareValues(value1: T, value2: T): boolean { try { return compareValues(value1, value2); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.error( `error comparing values\n${JSON.stringify(value1)}\n${JSON.stringify(value2)}`, diff --git a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts index a2f0c78cd52..0499f34a4ae 100644 --- a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts +++ b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts @@ -17,6 +17,8 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } diff --git a/apps/browser/src/popup/main.ts b/apps/browser/src/popup/main.ts index 8ffe0743bf6..dadd7917b99 100644 --- a/apps/browser/src/popup/main.ts +++ b/apps/browser/src/popup/main.ts @@ -4,7 +4,9 @@ import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/popup.scss"); +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/tailwind.css"); import { AppModule } from "./app.module"; diff --git a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts index 432754ab91c..21fa44b8d3f 100644 --- a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts +++ b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts @@ -12,6 +12,8 @@ describe("LpFilelessImporter", () => { chrome.runtime.connect = jest.fn(() => portSpy); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-fileless-importer"); lpFilelessImporter = (globalThis as any).lpFilelessImporter; }); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts index 95b49ea00ec..8479235cc17 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts @@ -7,6 +7,8 @@ describe("LP Suppress Import Download for Manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts index bfff3787506..ff0ed381599 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts @@ -10,6 +10,8 @@ describe("LP Suppress Import Download", () => { jest.spyOn(Element.prototype, "appendChild"); jest.spyOn(window, "addEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download"); anchor = document.createElement("a"); diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 5b351efe459..e26d073326e 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -34,6 +34,8 @@ export class ShareCommand { if (req == null || req.length === 0) { return Response.badRequest("You must provide at least one collection id for this item."); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 2a3d5d85408..359ed08ca99 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -165,6 +165,8 @@ export class LoginCommand { if (options.method != null) { twoFactorMethod = parseInt(options.method, null); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } @@ -240,6 +242,8 @@ export class LoginCommand { if (twoFactorMethod != null) { try { selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index dd99d03b086..8efb414f5b2 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -57,6 +57,8 @@ export class EditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/platform/flags.ts b/apps/cli/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/cli/src/platform/flags.ts +++ b/apps/cli/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/cli/src/platform/services/node-env-secure-storage.service.ts b/apps/cli/src/platform/services/node-env-secure-storage.service.ts index 5e9fdd26b3c..2807509e428 100644 --- a/apps/cli/src/platform/services/node-env-secure-storage.service.ts +++ b/apps/cli/src/platform/services/node-env-secure-storage.service.ts @@ -87,6 +87,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } return Utils.fromBufferToB64(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Decrypt error."); return null; @@ -104,6 +106,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Session key is invalid."); } diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts index eff351be22a..a9264c50126 100644 --- a/apps/cli/src/tools/send/commands/create.command.ts +++ b/apps/cli/src/tools/send/commands/create.command.ts @@ -49,6 +49,8 @@ export class SendCreateCommand { if (req == null) { throw new Error("Null request"); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/tools/send/commands/edit.command.ts b/apps/cli/src/tools/send/commands/edit.command.ts index 11508d5c417..ed719b58311 100644 --- a/apps/cli/src/tools/send/commands/edit.command.ts +++ b/apps/cli/src/tools/send/commands/edit.command.ts @@ -41,6 +41,8 @@ export class SendEditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = SendResponse.fromJson(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index d27ba4f88ec..41a6681af55 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -47,6 +47,8 @@ export class SendReceiveCommand extends DownloadCommand { let urlObject: URL; try { urlObject = new URL(url); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Failed to parse the provided Send url"); } diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 13cd666754f..a28d070d19e 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -66,6 +66,8 @@ export class CreateCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/desktop/config/config.js b/apps/desktop/config/config.js index 404295dd0db..30a5c16bb2a 100644 --- a/apps/desktop/config/config.js +++ b/apps/desktop/config/config.js @@ -32,6 +32,8 @@ function log(configObj) { function loadConfig(configName) { try { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports return require(`./${configName}.json`); } catch (e) { if (e instanceof Error && e.code === "MODULE_NOT_FOUND") { diff --git a/apps/desktop/postcss.config.js b/apps/desktop/postcss.config.js index c4513687e89..c39e7ea0355 100644 --- a/apps/desktop/postcss.config.js +++ b/apps/desktop/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports, no-undef */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index fd16cd5ffbe..30b17a44d12 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const child_process = require("child_process"); const path = require("path"); diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index dc60e9d1838..20c24c8a76b 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const path = require("path"); diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js index 4cb643c34d4..649fe3b6736 100644 --- a/apps/desktop/scripts/build-macos-extension.js +++ b/apps/desktop/scripts/build-macos-extension.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ const child = require("child_process"); const { exit } = require("process"); diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index c1a2fb3cffc..d2c984a6f24 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const concurrently = require("concurrently"); const rimraf = require("rimraf"); diff --git a/apps/desktop/sign.js b/apps/desktop/sign.js index 74c63932306..f0110ea195b 100644 --- a/apps/desktop/sign.js +++ b/apps/desktop/sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ exports.default = async function (configuration) { if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") { diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index e4ee5ec0473..e565681de93 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -67,6 +67,7 @@ import { SendComponent } from "./tools/send/send.component"; /** * Data properties acceptable for use in route objects in the desktop */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RouteDataProperties { // For any new route data properties, add them here. // then assert that the data object satisfies this interface in the route object. diff --git a/apps/desktop/src/app/main.ts b/apps/desktop/src/app/main.ts index a0b490edaa6..287d66795d2 100644 --- a/apps/desktop/src/app/main.ts +++ b/apps/desktop/src/app/main.ts @@ -1,7 +1,9 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; +// eslint-disable-next-line @typescript-eslint/no-require-imports require("../scss/styles.scss"); +// eslint-disable-next-line @typescript-eslint/no-require-imports require("../scss/tailwind.css"); import { AppModule } from "./app.module"; diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.ts b/apps/desktop/src/auth/login/desktop-login-component.service.ts index 8a2daef49ff..dbf689e801c 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.ts @@ -65,6 +65,8 @@ export class DesktopLoginComponentService try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.toastService.showToast({ variant: "error", diff --git a/apps/desktop/src/auth/login/login-v1.component.ts b/apps/desktop/src/auth/login/login-v1.component.ts index 5b1a1c68d29..e0c3f794dba 100644 --- a/apps/desktop/src/auth/login/login-v1.component.ts +++ b/apps/desktop/src/auth/login/login-v1.component.ts @@ -249,6 +249,8 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.platformUtilsService.showToast( "error", diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 6915a18deda..107d546811c 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -405,6 +405,8 @@ export class NativeMessagingMain { this.logService.info(`Error reading preferences: ${e}`); } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Browser is not installed, we can just skip it } diff --git a/apps/desktop/src/platform/flags.ts b/apps/desktop/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/desktop/src/platform/flags.ts +++ b/apps/desktop/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 72d00988e88..74f80785fca 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -265,6 +265,8 @@ export class BiometricMessageHandlerService { } else { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } @@ -342,6 +344,8 @@ export class BiometricMessageHandlerService { appId, ); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send( { command: BiometricsCommands.UnlockWithBiometricsForUser, messageId, response: false }, diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index 9a914f238b5..fa5c2f4d9f7 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -130,6 +130,8 @@ export class DuckDuckGoMessageHandlerService { sharedKey: Utils.fromBufferToB64(encryptedSecret), }, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.sendResponse({ messageId: messageId, @@ -153,6 +155,8 @@ export class DuckDuckGoMessageHandlerService { await this.encryptedMessageHandlerService.responseDataForCommand(decryptedCommandData); await this.sendEncryptedResponse(message, { command, payload: responseData }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // 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 diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 09c0fe2a07c..43c4b9065a7 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -178,6 +178,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } @@ -222,6 +224,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js index c4513687e89..c39e7ea0355 100644 --- a/apps/web/postcss.config.js +++ b/apps/web/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports, no-undef */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index 1080880a466..9d06be92eb8 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -167,6 +167,8 @@ export abstract class BaseEventsComponent { let dates: string[] = null; try { dates = this.eventService.formatDateFilters(this.start, this.end); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 1ba1431cbd5..58efc7348e1 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -113,6 +113,8 @@ export class EntityEventsComponent implements OnInit { this.filterFormGroup.value.start, this.filterFormGroup.value.end, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index b3c046d1fef..031bd7bf180 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -170,6 +170,8 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { // 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(["/"]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { if (this.showNewOrganization) { const dialog = openDeleteOrganizationDialog(this.dialogService, { diff --git a/apps/web/src/app/platform/web-sdk-client-factory.ts b/apps/web/src/app/platform/web-sdk-client-factory.ts index 2ebb2bcc10f..0dd43ecbb92 100644 --- a/apps/web/src/app/platform/web-sdk-client-factory.ts +++ b/apps/web/src/app/platform/web-sdk-client-factory.ts @@ -27,6 +27,8 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } diff --git a/apps/web/src/app/tools/send/send-access-file.component.ts b/apps/web/src/app/tools/send/send-access-file.component.ts index 0b2a971bbe8..b55e955f355 100644 --- a/apps/web/src/app/tools/send/send-access-file.component.ts +++ b/apps/web/src/app/tools/send/send-access-file.component.ts @@ -70,6 +70,8 @@ export class SendAccessFileComponent { blobData: decBuf, downloadMethod: "save", }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 88af1ef601b..bc639ff2f61 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -46,6 +46,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { dialogService, formBuilder, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions params?.folderId ? (this.folderId = params.folderId) : null; } diff --git a/apps/web/src/connectors/captcha.ts b/apps/web/src/connectors/captcha.ts index 01a14a79a23..aad6eaa3d47 100644 --- a/apps/web/src/connectors/captcha.ts +++ b/apps/web/src/connectors/captcha.ts @@ -5,8 +5,12 @@ import { b64Decode, getQsParam } from "./common"; declare let hcaptcha: any; if (window.location.pathname.includes("mobile")) { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha-mobile.scss"); } else { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha.scss"); } @@ -50,6 +54,8 @@ async function start() { let decodedData: any; try { decodedData = JSON.parse(b64Decode(data, true)); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index 0f067b583b9..b5300ff65e7 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -3,6 +3,8 @@ import { getQsParam } from "./common"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./duo-redirect.scss"); const mobileDesktopCallback = "bitwarden://duo-callback"; diff --git a/apps/web/src/connectors/sso.ts b/apps/web/src/connectors/sso.ts index 3ec6a8f7a3d..b48c2b49d72 100644 --- a/apps/web/src/connectors/sso.ts +++ b/apps/web/src/connectors/sso.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { getQsParam } from "./common"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./sso.scss"); window.addEventListener("load", () => { diff --git a/apps/web/src/connectors/webauthn-fallback.ts b/apps/web/src/connectors/webauthn-fallback.ts index 971bc5e44a1..6f32bbaecf8 100644 --- a/apps/web/src/connectors/webauthn-fallback.ts +++ b/apps/web/src/connectors/webauthn-fallback.ts @@ -4,6 +4,8 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); let parsed = false; @@ -52,6 +54,8 @@ function parseParametersV2() { let dataObj: { data: any; btnText: string } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -103,6 +107,8 @@ function start() { let json: any; try { json = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/webauthn.ts b/apps/web/src/connectors/webauthn.ts index 14ba8a280ee..32e6e0ab673 100644 --- a/apps/web/src/connectors/webauthn.ts +++ b/apps/web/src/connectors/webauthn.ts @@ -3,6 +3,8 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); const mobileCallbackUri = "bitwarden://webauthn-callback"; @@ -88,6 +90,8 @@ function parseParametersV2() { } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -116,6 +120,8 @@ function start() { try { obj = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse webauthn data."); return; diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index 760877ff8bc..ac8ad3112b9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -98,6 +98,8 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy { title: null, message: this.i18nService.t("loginRequestApproved"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.toastService.showToast({ variant: "error", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts index 9ae9b2acec3..c5934067fd7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts @@ -107,6 +107,8 @@ export class SecretsManagerPortingApiService { return secret; }), ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/libs/angular/src/auth/components/login-v1.component.ts b/libs/angular/src/auth/components/login-v1.component.ts index 0775114bd8c..ffe1dda3aed 100644 --- a/libs/angular/src/auth/components/login-v1.component.ts +++ b/libs/angular/src/auth/components/login-v1.component.ts @@ -397,6 +397,8 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni email, deviceIdentifier, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.showLoginWithDevice = false; } diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 425b4be2840..1a4c428aae2 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -192,6 +192,8 @@ export class AttachmentsComponent implements OnInit { title: null, message: this.i18nService.t("fileSavedToDevice"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } @@ -285,6 +287,8 @@ export class AttachmentsComponent implements OnInit { this.i18nService.t("attachmentSaved"), ); this.onReuploadedAttachment.emit(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 724a1507be1..ef9aff736ed 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -466,6 +466,8 @@ export class ViewComponent implements OnDestroy, OnInit { fileName: attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 5600077c363..8f22f391b13 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -107,6 +107,8 @@ export class LoginDecryptionOptionsComponent implements OnInit { private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private validationService: ValidationService, ) { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.clientType === this.platformUtilsService.getClientType(); } diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 40f85e6d75c..f9aaa5d1e05 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -484,6 +484,8 @@ export class LoginComponent implements OnInit, OnDestroy { try { const deviceIdentifier = await this.appIdService.getAppId(); this.isKnownDevice = await this.devicesApiService.getKnownDevice(email, deviceIdentifier); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.isKnownDevice = false; } diff --git a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts index 717e80b110d..24b3adacc21 100644 --- a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts +++ b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts @@ -18,6 +18,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { try { // Attempt to decode from URL-safe Base64 to UTF-8 decodedPayloadJSON = Utils.fromUrlB64ToUtf8(encodedPayload); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (decodingError) { throw new Error("Cannot decode the token"); } @@ -26,6 +28,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { // Attempt to parse the JSON payload const decodedToken = JSON.parse(decodedPayloadJSON); return decodedToken; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (jsonError) { throw new Error("Cannot parse the token's payload into JSON"); } diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index c40091a66fa..a94c8b6e422 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -337,6 +337,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); return new SymmetricCryptoKey(userKey) as UserKey; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // If either decryption effort fails, we want to remove the device key this.logService.error("Failed to decrypt using device key. Removing device key."); diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 3f0a0adfcb5..4735da32b6b 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -163,6 +163,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti const request = new VerifyOTPRequest(verification.secret); try { await this.userVerificationApiService.postAccountVerifyOTP(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidVerificationCode")); } @@ -221,6 +223,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti request.masterPasswordHash = serverKeyHash; try { policyOptions = await this.userVerificationApiService.postAccountVerifyPassword(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidMasterPassword")); } diff --git a/libs/common/src/platform/misc/flags.ts b/libs/common/src/platform/misc/flags.ts index 8ed19ce57fc..30531b6799e 100644 --- a/libs/common/src/platform/misc/flags.ts +++ b/libs/common/src/platform/misc/flags.ts @@ -1,14 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedFlags = { sdk?: boolean; prereleaseBuild?: boolean; }; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedDevFlags = { noopNotifications: boolean; skipWelcomeOnInstall: boolean; diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index 95d17e6d046..f654897e9e2 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -14,6 +14,8 @@ import { KeyService } from "../../../../key-management/src/abstractions/key.serv import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports const nodeURL = typeof self === "undefined" ? require("url") : null; declare global { @@ -610,6 +612,8 @@ export class Utils { } return new URL(uriString); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error } diff --git a/libs/common/src/platform/models/domain/domain-base.spec.ts b/libs/common/src/platform/models/domain/domain-base.spec.ts index 0bdee21e3c1..80a4e5e8606 100644 --- a/libs/common/src/platform/models/domain/domain-base.spec.ts +++ b/libs/common/src/platform/models/domain/domain-base.spec.ts @@ -67,9 +67,13 @@ describe("DomainBase", () => { ); // @ts-expect-error -- encString2 was not decrypted + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: string; plainText: string }; // encString2 was not decrypted, so it's still an EncString + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: EncString; plainText: string }; }); diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index 688cf52d4c0..110a1dc7208 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -8,7 +8,7 @@ import { EncryptService } from "../../abstractions/encrypt.service"; import { EncString } from "./enc-string"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type type EncStringKeys = ConditionalKeys, EncString>; export type DecryptedObject< TEncryptedObject, diff --git a/libs/common/src/platform/models/domain/enc-string.ts b/libs/common/src/platform/models/domain/enc-string.ts index b8e0006942a..f148664a4f9 100644 --- a/libs/common/src/platform/models/domain/enc-string.ts +++ b/libs/common/src/platform/models/domain/enc-string.ts @@ -125,6 +125,8 @@ export class EncString implements Encrypted { try { encType = parseInt(headerPieces[0], null); encPieces = headerPieces[1].split("|"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return { encType: NaN, encPieces: [] }; } @@ -186,6 +188,8 @@ export class EncString implements Encrypted { key, decryptTrace == null ? context : `${decryptTrace}${context || ""}`, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } @@ -203,6 +207,8 @@ export class EncString implements Encrypted { } this.decryptedValue = await encryptService.decryptToUtf8(this, key, decryptTrace); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } diff --git a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts index 53ae94c30b2..84bcaeec608 100644 --- a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const AutofillOverlayVisibility = { Off: 0, OnButtonClick: 1, diff --git a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts index 04b4a50e0ed..9397191f54b 100644 --- a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ClearClipboardDelay = { Never: null as null, TenSeconds: 10, diff --git a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts index b465a69131a..71e5c531d50 100644 --- a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const UriMatchStrategy = { Domain: 0, Host: 1, diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 43115b65937..c2390d439e7 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -81,6 +81,8 @@ export class Send extends Domain { const sendKeyEncryptionKey = await keyService.getUserKey(); model.key = await encryptService.decryptToBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/vault/icon/build-cipher-icon.ts b/libs/common/src/vault/icon/build-cipher-icon.ts index 8ffe4749568..5775bc7f55e 100644 --- a/libs/common/src/vault/icon/build-cipher-icon.ts +++ b/libs/common/src/vault/icon/build-cipher-icon.ts @@ -53,6 +53,8 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show try { image = `${iconsServerUrl}/${Utils.getHostname(hostnameUri)}/icon.png`; fallbackImage = "images/bwi-globe.png"; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error since the fallback icon will be shown if image is null. } diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 2b893e33f49..4eee0307746 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -69,6 +69,8 @@ export class Attachment extends Domain { const encryptService = Utils.getContainerService().getEncryptService(); const decValue = await encryptService.decryptToBytes(this.key, encKey); return new SymmetricCryptoKey(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index c5de01293ee..20dbd23065c 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -155,6 +155,8 @@ export class CipherView implements View, InitializerMetadata { return null; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars const item = this.item; return this.item[linkedFieldOption.propertyKey as keyof typeof item]; } diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index a6b7a0c0a22..315adb87c75 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -142,6 +142,8 @@ export class LoginUriView implements View { try { const regex = new RegExp(this.uri, "i"); return regex.test(targetUri); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Invalid regex return false; diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts index c8e9d3d2fe3..ef7ba68b65c 100644 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ b/libs/components/src/a11y/a11y-grid.directive.ts @@ -86,6 +86,8 @@ export class A11yGridDirective implements AfterViewInit { }); this.getActiveCellContent().tabIndex = 0; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // eslint-disable-next-line no-console console.error("Unable to initialize grid"); diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 58d93ddd3a4..d615bfe0582 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -70,6 +70,8 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI setOpen(isOpen: boolean) { this.open = isOpen; this.openChange.emit(this.open); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.open && this.parentNavGroup?.setOpen(this.open); } diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 8b497beac93..e8f63ef8705 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -267,7 +267,7 @@ describe("ImportService", () => { function createCipher(options: Partial = {}) { const cipher = new CipherView(); - cipher.name; + cipher.name = options.name; cipher.type = options.type; cipher.folderId = options.folderId; cipher.collectionIds = options.collectionIds; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 5425dbc53f9..98474b4babf 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -683,6 +683,8 @@ export class DefaultKeyService implements KeyServiceAbstraction { // failed to decrypt return false; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return false; } diff --git a/libs/shared/jest.config.angular.js b/libs/shared/jest.config.angular.js index de703e27220..311318e46a4 100644 --- a/libs/shared/jest.config.angular.js +++ b/libs/shared/jest.config.angular.js @@ -1,5 +1,5 @@ /* eslint-env node */ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const { defaultTransformerOptions } = require("jest-preset-angular/presets"); /** @type {import('jest').Config} */ diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts index f63a974d487..62d171b8436 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts @@ -38,6 +38,8 @@ describe("AddEditCustomFieldDialogComponent", () => { fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent); component = fixture.componentInstance; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions fixture.detectChanges; }); diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index b22ced3d899..a47cdc8d1d5 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -98,6 +98,8 @@ export class DownloadAttachmentComponent { fileName: this.attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/package-lock.json b/package-lock.json index 3d6ff011724..5366b26861b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,8 +120,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/eslint-plugin": "8.19.1", + "@typescript-eslint/parser": "8.19.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", @@ -10082,88 +10082,94 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", - "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", + "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/type-utils": "7.16.1", - "@typescript-eslint/utils": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/type-utils": "8.19.1", + "@typescript-eslint/utils": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", - "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", + "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/utils": "7.16.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/utils": "8.19.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", + "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/@typescript-eslint/experimental-utils": { @@ -10316,46 +10322,42 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz", - "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", + "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", + "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10363,13 +10365,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", + "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10377,32 +10379,43 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", + "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/@typescript-eslint/utils": { @@ -10513,23 +10526,36 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", + "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.19.1", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", diff --git a/package.json b/package.json index e6c476d2dfd..1090d3efb33 100644 --- a/package.json +++ b/package.json @@ -80,8 +80,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/eslint-plugin": "8.19.1", + "@typescript-eslint/parser": "8.19.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 80f70e47b09..941a612a30c 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -14,6 +14,7 @@ "declarationDir": "dist/types", "outDir": "dist", "baseUrl": ".", + "allowJs": true, "paths": { "@bitwarden/admin-console": ["./libs/admin-console/src"], "@bitwarden/angular/*": ["./libs/angular/src/*"], From 63a9c69f5a904bde34315fdff151e21eff59480f Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Tue, 14 Jan 2025 12:15:23 -0500 Subject: [PATCH 177/270] fix seat count to include invited and accepted members (#12848) --- .../components/member-dialog/member-dialog.component.ts | 6 +++--- .../organizations/members/members.component.ts | 7 ++++++- .../member-access-report/member-access-report.component.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 514e7701e4b..4f43dacad32 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -68,7 +68,7 @@ export interface MemberDialogParams { usesKeyConnector: boolean; isOnSecretsManagerStandalone: boolean; initialTab?: MemberDialogTab; - numConfirmedMembers: number; + numSeatsUsed: number; managedByOrganization?: boolean; } @@ -263,7 +263,7 @@ export class MemberDialogComponent implements OnDestroy { }); this.remainingSeats$ = this.organization$.pipe( - map((organization) => organization.seats - this.params.numConfirmedMembers), + map((organization) => organization.seats - this.params.numSeatsUsed), ); } @@ -458,7 +458,7 @@ export class MemberDialogComponent implements OnDestroy { } if ( organization.hasReseller && - this.params.numConfirmedMembers + emails.length > organization.seats + this.params.numSeatsUsed + emails.length > organization.seats ) { this.formGroup.controls.emails.setErrors({ tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") }, diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 3d10a4a07b3..703f187b223 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -510,6 +510,11 @@ export class MembersComponent extends BaseMembersComponent return; } + const numSeatsUsed = + this.dataSource.confirmedUserCount + + this.dataSource.invitedUserCount + + this.dataSource.acceptedUserCount; + const dialog = openUserAddEditDialog(this.dialogService, { data: { name: this.userNamePipe.transform(user), @@ -519,7 +524,7 @@ export class MembersComponent extends BaseMembersComponent usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: initialTab, - numConfirmedMembers: this.dataSource.confirmedUserCount, + numSeatsUsed, managedByOrganization: user?.managedByOrganization, }, }); diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts index 6aaaf1a4066..321aae165c5 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -103,7 +103,7 @@ export class MemberAccessReportComponent implements OnInit { usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: MemberDialogTab.Role, - numConfirmedMembers: this.dataSource.data.length, + numSeatsUsed: this.dataSource.data.length, }, }); From 318a3ac6a9718e8cde7455095fb441fd7344031a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 14 Jan 2025 18:29:52 +0100 Subject: [PATCH 178/270] [PM-17038] Fix biometrics autoprompt in firefox and chrome (#12853) * Fix biometrics not working in firefox or windows * Remove logs * Update badge after biometric unlock * Add removal todo note * Remove debug logging * Fix type warnings * Fix userkey typing in background biometrics service * Simplify types for userkey in foreground-browser-biometrics and runtime.background.ts * Add process reload logging * Fix autoprompt not working when no process reload happened * Fix instant reprompt on firefox lock * Fix biometrics enabling error on chrome * Update apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * FIx build & linting --------- Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> --- .../biometrics/foreground-browser-biometrics.ts | 3 ++- apps/browser/src/popup/app.component.ts | 6 ++++-- .../services/default-process-reload.service.ts | 11 +++++++++++ .../src/angular/lock/components/lock.component.ts | 8 +++++--- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index 43cc25e4dad..d248a630cc6 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -1,3 +1,4 @@ +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { BiometricsCommands, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; @@ -34,7 +35,7 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { if (!response.result) { return null; } - return response.result; + return SymmetricCryptoKey.fromString(response.result.keyB64) as UserKey; } async getBiometricsStatusForUser(id: UserId): Promise { diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index e8a660620a9..7b6e402a90f 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -21,7 +21,7 @@ import { ToastOptions, ToastService, } from "@bitwarden/components"; -import { BiometricStateService } from "@bitwarden/key-management"; +import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service"; @@ -66,6 +66,7 @@ export class AppComponent implements OnInit, OnDestroy { private accountService: AccountService, private animationControlService: AnimationControlService, private biometricStateService: BiometricStateService, + private biometricsService: BiometricsService, ) {} async ngOnInit() { @@ -102,7 +103,7 @@ export class AppComponent implements OnInit, OnDestroy { this.messageListener.allMessages$ .pipe( - tap((msg: any) => { + tap(async (msg: any) => { if (msg.command === "doneLoggingOut") { // TODO: PM-8544 - why do we call logout in the popup after receiving the doneLoggingOut message? Hasn't this already completeted logout? this.authService.logOut(async () => { @@ -119,6 +120,7 @@ export class AppComponent implements OnInit, OnDestroy { msg.command === "locked" && (msg.userId == null || msg.userId == this.activeUserId) ) { + await this.biometricsService.setShouldAutopromptNow(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(["lock"]); diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 3f099b77328..9f97e0a94c1 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -39,6 +39,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract let status = await firstValueFrom(authService.authStatusFor$(userId as UserId)); status = await authService.getAuthStatus(userId); if (status === AuthenticationStatus.Unlocked) { + this.logService.info( + "[Process Reload Service] User unlocked, preventing process reload", + ); return; } } @@ -55,6 +58,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract if (userId != null) { const ephemeralPin = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId); if (ephemeralPin != null) { + this.logService.info( + "[Process Reload Service] Ephemeral pin active, preventing process reload", + ); return; } } @@ -97,7 +103,12 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract await this.reloadCallback(); } return; + } else { + this.logService.info( + "[Process Reload Service] Desktop ipc fingerprint validated, preventing process reload", + ); } + if (this.reloadInterval == null) { this.reloadInterval = setInterval(async () => await this.executeProcessReload(), 1000); } diff --git a/libs/key-management/src/angular/lock/components/lock.component.ts b/libs/key-management/src/angular/lock/components/lock.component.ts index 23f1a7a4330..6c912b0eaae 100644 --- a/libs/key-management/src/angular/lock/components/lock.component.ts +++ b/libs/key-management/src/angular/lock/components/lock.component.ts @@ -307,10 +307,12 @@ export class LockComponent implements OnInit, OnDestroy { (await this.biometricService.getShouldAutopromptNow()) ) { await this.biometricService.setShouldAutopromptNow(false); + + const lastProcessReload = await this.biometricStateService.getLastProcessReload(); if ( - (await this.biometricStateService.getLastProcessReload()) == null || - Date.now() - (await this.biometricStateService.getLastProcessReload()).getTime() > - AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY + lastProcessReload == null || + isNaN(lastProcessReload.getTime()) || + Date.now() - lastProcessReload.getTime() > AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY ) { await this.unlockViaBiometrics(); } From eedf9af2e79e8e53844f4a4dd35dbaf11f07e071 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:39:43 +0100 Subject: [PATCH 179/270] in desktop "Allow browser integration" button fails on dev environment, but should pass fine. (#12797) Co-authored-by: aj-bw <81774843+aj-bw@users.noreply.github.com> --- apps/desktop/src/app/accounts/settings.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 19748e797bb..f3440975cf2 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -650,7 +650,7 @@ export class SettingsComponent implements OnInit, OnDestroy { const skipSupportedPlatformCheck = ipc.platform.allowBrowserintegrationOverride || ipc.platform.isDev; - if (skipSupportedPlatformCheck) { + if (!skipSupportedPlatformCheck) { if ( ipc.platform.deviceType === DeviceType.MacOsDesktop && !this.platformUtilsService.isMacAppStore() From f44b36bdf62ff192ea3da59654f24c08bcd0f0bc Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 14 Jan 2025 18:48:50 +0100 Subject: [PATCH 180/270] Fix linting conflicts after merge (#12864) --- .../src/auth/popup/settings/account-security.component.ts | 2 ++ .../src/vault/popup/services/vault-popup-autofill.service.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 1a64d860e45..7e094fe508b 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -514,6 +514,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { try { const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); result = await this.keyService.validateUserKey(userKey, userId); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { result = false; } diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index c0ac9c91e18..ff282d7a6d0 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -127,6 +127,8 @@ export class VaultPopupAutofillService { [currentTabHostname as string]: { bannerIsDismissed: true }, }); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error( "There was a problem dismissing the blocked interaction URI notification banner", From 8621e8aa6a2b6636a95359e13a32c8fdc1b19d73 Mon Sep 17 00:00:00 2001 From: Graham Walker Date: Tue, 14 Jan 2025 13:16:26 -0600 Subject: [PATCH 181/270] PM-16170 remove methods using deprecated send endpoints (#12751) --- .../services/send-api.service.abstraction.ts | 5 --- .../tools/send/services/send-api.service.ts | 45 +------------------ 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/libs/common/src/tools/send/services/send-api.service.abstraction.ts b/libs/common/src/tools/send/services/send-api.service.abstraction.ts index a6427824a64..570f3e746a0 100644 --- a/libs/common/src/tools/send/services/send-api.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send-api.service.abstraction.ts @@ -22,11 +22,6 @@ export abstract class SendApiService { postSend: (request: SendRequest) => Promise; postFileTypeSend: (request: SendRequest) => Promise; postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postSendFileLegacy: (data: FormData) => Promise; putSend: (id: string, request: SendRequest) => Promise; putSendRemovePassword: (id: string) => Promise; deleteSend: (id: string) => Promise; diff --git a/libs/common/src/tools/send/services/send-api.service.ts b/libs/common/src/tools/send/services/send-api.service.ts index ff71408bce3..f709553646f 100644 --- a/libs/common/src/tools/send/services/send-api.service.ts +++ b/libs/common/src/tools/send/services/send-api.service.ts @@ -5,7 +5,6 @@ import { FileUploadApiMethods, FileUploadService, } from "../../../platform/abstractions/file-upload/file-upload.service"; -import { Utils } from "../../../platform/misc/utils"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { SendType } from "../enums/send-type"; import { SendData } from "../models/data/send.data"; @@ -106,15 +105,6 @@ export class SendApiService implements SendApiServiceAbstraction { return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); } - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postSendFileLegacy(data: FormData): Promise { - const r = await this.apiService.send("POST", "/sends/file", data, true, true); - return new SendResponse(r); - } - async putSend(id: string, request: SendRequest): Promise { const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true); return new SendResponse(r); @@ -173,9 +163,7 @@ export class SendApiService implements SendApiServiceAbstraction { this.generateMethods(uploadDataResponse, response), ); } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - response = await this.legacyServerSendFileUpload(sendData, request); - } else if (e instanceof ErrorResponse) { + if (e instanceof ErrorResponse) { throw new Error((e as ErrorResponse).getSingleMessage()); } else { throw e; @@ -219,35 +207,4 @@ export class SendApiService implements SendApiServiceAbstraction { return this.deleteSend(sendId); }; } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerSendFileUpload( - sendData: [Send, EncArrayBuffer], - request: SendRequest, - ): Promise { - const fd = new FormData(); - try { - const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); - fd.append("model", JSON.stringify(request)); - fd.append("data", blob, sendData[0].file.fileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("model", JSON.stringify(request)); - fd.append( - "data", - Buffer.from(sendData[1].buffer) as any, - { - filepath: sendData[0].file.fileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - return await this.postSendFileLegacy(fd); - } } From 27e8a1f27c4477ad645259de90db4e1beb3b9eeb Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:23:51 -0800 Subject: [PATCH 182/270] fix(auth) [PM-17047] Change clientType expression to assignment (#12865) Fixes a bug where we had an expression (`===`) that should be an assignment (`=`). Feature Flag: UnauthenticatedExtensionUIRefresh --- .../login-decryption-options.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 8f22f391b13..a3f5e062e4f 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -107,9 +107,7 @@ export class LoginDecryptionOptionsComponent implements OnInit { private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private validationService: ValidationService, ) { - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - this.clientType === this.platformUtilsService.getClientType(); + this.clientType = this.platformUtilsService.getClientType(); } async ngOnInit() { From f2b6f05d3f007500a843ce1c737b1ceda9379606 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 14 Jan 2025 13:58:57 -0600 Subject: [PATCH 183/270] PM-16891 Applications at risk dialog (#12843) * Org at risk members click on the card * Fixing at risk member counts * At risk member text modification * Changing ok button to close * PM-16891 added a dialog for at risk apps * PM-16891 fixing order of imports (linting error) * PM-16891 updated PR comments --------- Co-authored-by: Tom Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com> --- apps/web/src/locales/en/messages.json | 33 +++++++++++++++++++ .../risk-insights/models/password-health.ts | 9 +++++ .../services/risk-insights-report.service.ts | 25 ++++++++++++++ .../all-applications.component.html | 3 +- .../all-applications.component.ts | 7 ++++ .../org-at-risk-apps-dialog.component.html | 25 ++++++++++++++ .../org-at-risk-apps-dialog.component.ts | 24 ++++++++++++++ 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 2779c0470e7..eacba623ecd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -122,6 +122,39 @@ } } }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "atRiskMembersDescription": { "message": "These members are logging into applications with weak, exposed, or reused passwords." }, diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index ad87f319e73..94dad65fdc9 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -100,3 +100,12 @@ export type AtRiskMemberDetail = { email: string; atRiskPasswordCount: number; }; + +/* + * A list of applications and the count of + * at risk passwords for each application + */ +export type AtRiskApplicationDetail = { + applicationName: string; + atRiskPasswordCount: number; +}; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts index d97550b5887..c3bcc59eca5 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts @@ -13,6 +13,7 @@ import { ApplicationHealthReportDetail, ApplicationHealthReportSummary, AtRiskMemberDetail, + AtRiskApplicationDetail, CipherHealthReportDetail, CipherHealthReportUriDetail, ExposedPasswordDetail, @@ -114,6 +115,30 @@ export class RiskInsightsReportService { })); } + generateAtRiskApplicationList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskApplicationDetail[] { + const appsRiskMap = new Map(); + + cipherHealthReportDetails + .filter((app) => app.atRiskPasswordCount > 0) + .forEach((app) => { + if (appsRiskMap.has(app.applicationName)) { + appsRiskMap.set( + app.applicationName, + appsRiskMap.get(app.applicationName) + app.atRiskPasswordCount, + ); + } else { + appsRiskMap.set(app.applicationName, app.atRiskPasswordCount); + } + }); + + return Array.from(appsRiskMap.entries()).map(([applicationName, atRiskPasswordCount]) => ({ + applicationName, + atRiskPasswordCount, + })); + } + /** * Gets the summary from the application health report. Returns total members and applications as well * as the total at risk members and at risk applications diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html index 0493f7e44b8..e17ac078687 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html @@ -35,10 +35,11 @@ >
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index 00708de282f..5fb12fed090 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -32,6 +32,7 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component"; +import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component"; import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @@ -154,6 +155,12 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { }); }; + showOrgAtRiskApps = async () => { + this.dialogService.open(OrgAtRiskAppsDialogComponent, { + data: this.reportService.generateAtRiskApplicationList(this.dataSource.data), + }); + }; + onCheckboxChange(id: number, event: Event) { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html new file mode 100644 index 00000000000..298011b2157 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html @@ -0,0 +1,25 @@ + + + {{ "atRiskApplicationsWithCount" | i18n: atRiskApps.length }} + + +
    + {{ "atRiskApplicationsDescription" | i18n }} +
    +
    {{ "application" | i18n }}
    +
    {{ "atRiskPasswords" | i18n }}
    +
    + +
    +
    {{ app.applicationName }}
    +
    {{ app.atRiskPasswordCount }}
    +
    +
    +
    +
    + + + +
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts new file mode 100644 index 00000000000..0ae00f60874 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts @@ -0,0 +1,24 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AtRiskApplicationDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; + +export const openOrgAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AtRiskApplicationDetail[], +) => + dialogService.open(OrgAtRiskAppsDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./org-at-risk-apps-dialog.component.html", + imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], +}) +export class OrgAtRiskAppsDialogComponent { + constructor(@Inject(DIALOG_DATA) protected atRiskApps: AtRiskApplicationDetail[]) {} +} From 39a5addddc4a1a1d5d0696113663e8af1abd013a Mon Sep 17 00:00:00 2001 From: Github Actions Date: Tue, 14 Jan 2025 20:07:32 +0000 Subject: [PATCH 184/270] Bumped Desktop client to 2025.1.2 --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b8541aad2ec..8f6c6525a39 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.1", + "version": "2025.1.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index d8705487d86..d878e1af2aa 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.1", + "version": "2025.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.1", + "version": "2025.1.2", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 95490ee34dd..08bdd745063 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.1", + "version": "2025.1.2", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 5366b26861b..6b8d35c6d43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -231,7 +231,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.1.1", + "version": "2025.1.2", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -30884,6 +30884,7 @@ "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16" }, From 04566488dc827b95cb0e058b9c107165f2dcedac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 14 Jan 2025 15:18:14 -0500 Subject: [PATCH 185/270] add close button to passoword history dialog (#12790) --- ...redential-generator-history-dialog.component.html | 6 ++++++ .../credential-generator-history-dialog.component.ts | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.html b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html index b07eb62ae98..ad629601c34 100644 --- a/libs/tools/generator/components/src/credential-generator-history-dialog.component.html +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html @@ -14,5 +14,11 @@ > {{ "clearHistory" | i18n }} + + diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts index cec818b1cd6..7bcffd92399 100644 --- a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -34,6 +35,7 @@ export class CredentialGeneratorHistoryDialogComponent { private accountService: AccountService, private history: GeneratorHistoryService, private dialogService: DialogService, + private dialogRef: DialogRef, ) { this.accountService.activeAccount$ .pipe( @@ -52,7 +54,13 @@ export class CredentialGeneratorHistoryDialogComponent { .subscribe(this.hasHistory$); } - clear = async () => { + /** closes the dialog */ + protected close() { + this.dialogRef.close(); + } + + /** Launches clear history flow */ + protected async clear() { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "clearGeneratorHistoryTitle" }, content: { key: "cleargGeneratorHistoryDescription" }, @@ -64,5 +72,5 @@ export class CredentialGeneratorHistoryDialogComponent { if (confirmed) { await this.history.clear(await firstValueFrom(this.userId$)); } - }; + } } From 553d20f7a7aabf2ddf357efb1c4458a1071cad45 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:42:49 -0500 Subject: [PATCH 186/270] [deps] AC: Update bufferutil to v4.0.9 (#12700) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jimmy Vo --- 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 6b8d35c6d43..87e0354f7ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "bootstrap": "4.6.0", "braintree-web-drop-in": "1.43.0", "buffer": "6.0.3", - "bufferutil": "4.0.8", + "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", @@ -12728,9 +12728,9 @@ "license": "MIT" }, "node_modules/bufferutil": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", - "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1090d3efb33..641a49c4d04 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "bootstrap": "4.6.0", "braintree-web-drop-in": "1.43.0", "buffer": "6.0.3", - "bufferutil": "4.0.8", + "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", From 5fb383d7dc3b0b186e83533f8a3c44bf84c6ccd5 Mon Sep 17 00:00:00 2001 From: Evan Bassler Date: Tue, 14 Jan 2025 16:10:57 -0600 Subject: [PATCH 187/270] [PM-16248] add notification refresh feature flag (#12859) * add notification refresh feature flag * update order --------- Co-authored-by: Evan Bassler --- libs/common/src/enums/feature-flag.enum.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index feffe2ca442..dde31acb9e3 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -13,6 +13,7 @@ export enum FeatureFlag { InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", InlineMenuTotp = "inline-menu-totp", NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", + NotificationRefresh = "notification-refresh", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", BrowserFilelessImport = "browser-fileless-import", @@ -70,6 +71,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, [FeatureFlag.InlineMenuTotp]: FALSE, [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, + [FeatureFlag.NotificationRefresh]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.BrowserFilelessImport]: FALSE, From 6f018e1b2e74f0bdf003a2187bccbee14ec4a7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Tue, 14 Jan 2025 22:11:29 +0000 Subject: [PATCH 188/270] Fix claimed domains page for manage SSO users by replacing policies server check with policy service (#12863) --- .../domain-verification.component.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts index 2a2ae73227a..1cbe57a7082 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts @@ -16,7 +16,7 @@ import { import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; import { OrgDomainServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction"; import { OrganizationDomainResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain.response"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { HttpStatusCode } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -54,7 +54,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { private validationService: ValidationService, private toastService: ToastService, private configService: ConfigService, - private policyApiService: PolicyApiServiceAbstraction, + private policyService: PolicyService, ) { this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.AccountDeprovisioning, @@ -83,9 +83,14 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { await this.orgDomainApiService.getAllByOrgId(this.organizationId); if (await this.configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning)) { - const singleOrgPolicy = await this.policyApiService.getPolicy( - this.organizationId, - PolicyType.SingleOrg, + const singleOrgPolicy = await firstValueFrom( + this.policyService.policies$.pipe( + map((policies) => + policies.find( + (p) => p.type === PolicyType.SingleOrg && p.organizationId === this.organizationId, + ), + ), + ), ); this.singleOrgPolicyEnabled = singleOrgPolicy?.enabled ?? false; } From 55e4b5ee09a8bfce868f614eca27ddc3f32ef74d Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Wed, 15 Jan 2025 05:41:44 -0500 Subject: [PATCH 189/270] fix: Use `WeakMap` in `DerivedStateProvider` to separate user state caches (#12866) Bug fix for PM-15914 where switching users would incorrectly share cached derived states. The `DerivedStateProvider` now uses a `WeakMap` to maintain separate caches for each user's state `Observable`. - Modifies `DefaultDerivedStateProvider` to use `WeakMap` for caching - Each user's state `Observable` gets its own definition cache - Added test to verify correct behavior during user switching - Allows proper garbage collection of unused state caches This fixes issues where: - Users would see other users' cached states after switching accounts - Derived states weren't properly isolated between users - Cache keys didn't distinguish between different user states --- .../default-derived-state.provider.ts | 19 +++++++++++--- .../default-derived-state.spec.ts | 26 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts index 3c8c39e21e8..61f36fa0b75 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts @@ -8,7 +8,14 @@ import { DerivedStateProvider } from "../derived-state.provider"; import { DefaultDerivedState } from "./default-derived-state"; export class DefaultDerivedStateProvider implements DerivedStateProvider { - private cache: Record> = {}; + /** + * The cache uses a WeakMap to maintain separate derived states per user. + * Each user's state Observable acts as a unique key, without needing to + * pass around `userId`. Also, when a user's state Observable is cleaned up + * (like during an account swap) their cache is automatically garbage + * collected. + */ + private cache = new WeakMap, Record>>(); constructor() {} @@ -17,8 +24,14 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { + let stateCache = this.cache.get(parentState$); + if (!stateCache) { + stateCache = {}; + this.cache.set(parentState$, stateCache); + } + const cacheKey = deriveDefinition.buildCacheKey(); - const existingDerivedState = this.cache[cacheKey]; + const existingDerivedState = stateCache[cacheKey]; if (existingDerivedState != null) { // I have to cast out of the unknown generic but this should be safe if rules // around domain token are made @@ -26,7 +39,7 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { } const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies); - this.cache[cacheKey] = newDerivedState; + stateCache[cacheKey] = newDerivedState; return newDerivedState; } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts index 7e8d76bd203..6fcc1c408cb 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts @@ -9,6 +9,7 @@ import { DeriveDefinition } from "../derive-definition"; import { StateDefinition } from "../state-definition"; import { DefaultDerivedState } from "./default-derived-state"; +import { DefaultDerivedStateProvider } from "./default-derived-state.provider"; let callCount = 0; const cleanupDelayMs = 10; @@ -182,4 +183,29 @@ describe("DefaultDerivedState", () => { expect(await firstValueFrom(observable)).toEqual(new Date(newDate)); }); }); + + describe("account switching", () => { + let provider: DefaultDerivedStateProvider; + + beforeEach(() => { + provider = new DefaultDerivedStateProvider(); + }); + + it("should provide a dedicated cache for each account", async () => { + const user1State$ = new Subject(); + const user1Derived = provider.get(user1State$, deriveDefinition, deps); + const user1Emissions = trackEmissions(user1Derived.state$); + + const user2State$ = new Subject(); + const user2Derived = provider.get(user2State$, deriveDefinition, deps); + const user2Emissions = trackEmissions(user2Derived.state$); + + user1State$.next("2015-12-30"); + user2State$.next("2020-12-29"); + await awaitAsync(); + + expect(user1Emissions).toEqual([new Date("2015-12-30")]); + expect(user2Emissions).toEqual([new Date("2020-12-29")]); + }); + }); }); From ee6822c00d70c4eace5109b96bb78f2a9fe661ee Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Wed, 15 Jan 2025 16:05:31 +0100 Subject: [PATCH 190/270] [PM-17064] Prevent error being thrown when taxInformation is undefined. (#12884) --- .../billing/organizations/change-plan-dialog.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 bc5c7da8db9..73577c7b002 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 @@ -1062,7 +1062,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } private refreshSalesTax(): void { - if (!this.taxInformation.country || !this.taxInformation.postalCode) { + if ( + this.taxInformation === undefined || + !this.taxInformation.country || + !this.taxInformation.postalCode + ) { return; } From bdab4aa939e2d41f37afef666e9877a5b102b3f4 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:29:37 +0100 Subject: [PATCH 191/270] PM-17042: Biometrics auto prompt popup does not show up on safari (#12868) This is due to missing await before process reload, triggered by lock, effectively disabling the biometrics auto prompt on safari. This should be detected by eslint, but due to misconfiguration, nothing was reported. Also fixed two other missing awaits on biometrics unlock. --- .../biometrics/background-browser-biometrics.service.ts | 4 ++-- apps/browser/src/popup/app.component.ts | 4 ++-- tsconfig.eslint.json | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 4c4753c3f7f..e943f241f77 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -97,7 +97,7 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { if (await this.keyService.validateUserKey(userKey, userId)) { await this.biometricStateService.setBiometricUnlockEnabled(true); await this.biometricStateService.setFingerprintValidated(true); - this.keyService.setUserKey(userKey, userId); + await this.keyService.setUserKey(userKey, userId); return userKey; } } else { @@ -115,7 +115,7 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { if (await this.keyService.validateUserKey(userKey, userId)) { await this.biometricStateService.setBiometricUnlockEnabled(true); await this.biometricStateService.setFingerprintValidated(true); - this.keyService.setUserKey(userKey, userId); + await this.keyService.setUserKey(userKey, userId); return userKey; } } else { diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 7b6e402a90f..9d4835889b9 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -137,8 +137,8 @@ export class AppComponent implements OnInit, OnDestroy { this.toastService._showToast(msg); } else if (msg.command === "reloadProcess") { if (this.platformUtilsService.isSafari()) { - window.setTimeout(() => { - this.biometricStateService.updateLastProcessReload(); + window.setTimeout(async () => { + await this.biometricStateService.updateLastProcessReload(); window.location.reload(); }, 2000); } diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 941a612a30c..a69452389f5 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -36,6 +36,8 @@ "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/vault": ["./libs/vault/src"], + "@bitwarden/key-management": ["./libs/key-management/src"], + "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] }, "plugins": [ From 8c13ea894ba9d88d511aa533c071c71b92051bf2 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:43:26 +1000 Subject: [PATCH 192/270] [PM-16917] Remove jest-extended dependency (#12798) * add toContainPartialObjects matcher (replacing toIncludeAllPartialMembers from jest-extended) * replace jest-extended matchers with equivalent default matchers --- .github/renovate.json | 2 +- .../vault-header-v2.component.spec.ts | 2 +- .../view-v2/view-v2.component.spec.ts | 12 +-- .../vault-popup-list-filters.service.spec.ts | 4 +- .../default-vnext-collection.service.spec.ts | 10 +-- libs/admin-console/test.setup.ts | 4 + libs/admin-console/tsconfig.json | 2 +- libs/common/spec/matchers/index.ts | 12 +-- .../to-contain-partial-objects.spec.ts | 77 +++++++++++++++++++ .../matchers/to-contain-partial-objects.ts | 31 ++++++++ ...-service-legacy-encryptor-provider.spec.ts | 12 +-- libs/common/src/tools/rx.spec.ts | 2 +- .../services/folder/folder.service.spec.ts | 4 +- package-lock.json | 24 +----- package.json | 2 +- 15 files changed, 146 insertions(+), 54 deletions(-) create mode 100644 libs/common/spec/matchers/to-contain-partial-objects.spec.ts create mode 100644 libs/common/spec/matchers/to-contain-partial-objects.ts diff --git a/.github/renovate.json b/.github/renovate.json index 776c66af68e..a1987ca038d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -210,10 +210,10 @@ "eslint-plugin-storybook", "eslint-plugin-tailwindcss", "husky", - "jest-extended", "jest-junit", "jest-mock-extended", "jest-preset-angular", + "jest-diff", "lint-staged", "ts-jest" ], diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts index 38ec6056d19..1f67dd51c21 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts @@ -152,7 +152,7 @@ describe("VaultHeaderV2Component", () => { it("defaults the initial state to true", (done) => { // The initial value of the `state$` variable above is undefined component["initialDisclosureVisibility$"].subscribe((initialVisibility) => { - expect(initialVisibility).toBeTrue(); + expect(initialVisibility).toBe(true); done(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 7ee15aa833b..526ab2e2579 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -179,7 +179,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(doAutofill).toHaveBeenCalledOnce(); + expect(doAutofill).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-username"', fakeAsync(() => { @@ -187,7 +187,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-password"', fakeAsync(() => { @@ -195,7 +195,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-totp"', fakeAsync(() => { @@ -203,7 +203,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it("closes the popout after a load action", fakeAsync(() => { @@ -218,9 +218,9 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(doAutofill).toHaveBeenCalledOnce(); + expect(doAutofill).toHaveBeenCalledTimes(1); expect(focusSpy).toHaveBeenCalledWith(99); - expect(closeSpy).toHaveBeenCalledOnce(); + expect(closeSpy).toHaveBeenCalledTimes(1); })); }); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 0eb91c6cbe2..e1236be08f9 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -488,7 +488,7 @@ describe("VaultPopupListFiltersService", () => { state$.next(true); service.filterVisibilityState$.subscribe((filterVisibility) => { - expect(filterVisibility).toBeTrue(); + expect(filterVisibility).toBe(true); done(); }); }); @@ -496,7 +496,7 @@ describe("VaultPopupListFiltersService", () => { it("updates stored filter state", async () => { await service.updateFilterVisibility(false); - expect(update).toHaveBeenCalledOnce(); + expect(update).toHaveBeenCalledTimes(1); // Get callback passed to `update` const updateCallback = update.mock.calls[0][0]; expect(updateCallback()).toBe(false); diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts index 4aa54429aad..048a4733948 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts @@ -91,7 +91,7 @@ describe("DefaultvNextCollectionService", () => { // Assert emitted values expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: "DEC_NAME_" + collection1.id, @@ -167,7 +167,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -205,7 +205,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("UPDATED_ENC_NAME_" + collection1.id), @@ -230,7 +230,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -253,7 +253,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: newCollection3.id, name: makeEncString("ENC_NAME_" + newCollection3.id), diff --git a/libs/admin-console/test.setup.ts b/libs/admin-console/test.setup.ts index 6be6e7b8dd1..8ab102f2cf4 100644 --- a/libs/admin-console/test.setup.ts +++ b/libs/admin-console/test.setup.ts @@ -1,6 +1,10 @@ import { webcrypto } from "crypto"; + +import { addCustomMatchers } from "@bitwarden/common/spec"; import "jest-preset-angular/setup-jest"; +addCustomMatchers(); + Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { value: () => { diff --git a/libs/admin-console/tsconfig.json b/libs/admin-console/tsconfig.json index 3d22cb2ec51..4f057fd6af0 100644 --- a/libs/admin-console/tsconfig.json +++ b/libs/admin-console/tsconfig.json @@ -8,6 +8,6 @@ "@bitwarden/key-management": ["../key-management/src"] } }, - "include": ["src", "spec"], + "include": ["src", "spec", "../../libs/common/custom-matchers.d.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/common/spec/matchers/index.ts b/libs/common/spec/matchers/index.ts index 44440be5b54..b2e09cc8e92 100644 --- a/libs/common/spec/matchers/index.ts +++ b/libs/common/spec/matchers/index.ts @@ -1,16 +1,12 @@ -import * as matchers from "jest-extended"; - import { toBeFulfilled, toBeResolved, toBeRejected } from "./promise-fulfilled"; import { toAlmostEqual } from "./to-almost-equal"; +import { toContainPartialObjects } from "./to-contain-partial-objects"; import { toEqualBuffer } from "./to-equal-buffer"; export * from "./to-equal-buffer"; export * from "./to-almost-equal"; export * from "./promise-fulfilled"; -// add all jest-extended matchers -expect.extend(matchers); - export function addCustomMatchers() { expect.extend({ toEqualBuffer: toEqualBuffer, @@ -18,6 +14,7 @@ export function addCustomMatchers() { toBeFulfilled: toBeFulfilled, toBeResolved: toBeResolved, toBeRejected: toBeRejected, + toContainPartialObjects, }); } @@ -59,4 +56,9 @@ export interface CustomMatchers { * @returns CustomMatcherResult indicating whether or not the test passed */ toBeRejected(withinMs?: number): Promise; + /** + * Matches if the received array contains all the expected objects using partial matching (expect.objectContaining). + * @param expected An array of partial objects that should be contained in the received array. + */ + toContainPartialObjects(expected: Array): R; } diff --git a/libs/common/spec/matchers/to-contain-partial-objects.spec.ts b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts new file mode 100644 index 00000000000..ab6f90adf17 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts @@ -0,0 +1,77 @@ +describe("toContainPartialObjects", () => { + describe("matches", () => { + it("if the array only contains the partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + + it("if the array contains the partial objects and other objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + { + id: 3, + name: "baz", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + }); + + describe("doesn't match", () => { + it("if the array does not contain any partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1, name: "Foo" }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + + it("if the array contains some but not all partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 2 }, { id: 3 }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + }); +}); diff --git a/libs/common/spec/matchers/to-contain-partial-objects.ts b/libs/common/spec/matchers/to-contain-partial-objects.ts new file mode 100644 index 00000000000..f072ca6fba6 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.ts @@ -0,0 +1,31 @@ +import { EOL } from "os"; + +import { diff } from "jest-diff"; + +export const toContainPartialObjects: jest.CustomMatcher = function ( + received: Array, + expected: Array, +) { + const matched = this.equals( + received, + expect.arrayContaining(expected.map((e) => expect.objectContaining(e))), + ); + + if (matched) { + return { + message: () => + "Expected the received array NOT to include partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: true, + }; + } + + return { + message: () => + "Expected the received array to contain partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: false, + }; +}; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts index 831cad74155..0b60aef4917 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -184,7 +184,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleUserId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `userKey$` emits a falsy value after emitting a truthy value", () => { @@ -199,7 +199,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleUserId$` emits and `userKey$` completes", () => { @@ -214,7 +214,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); @@ -445,7 +445,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleOrganizationId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `orgKeys$` emits a falsy value after emitting a truthy value", () => { @@ -466,7 +466,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.next(OrgRecords); orgKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleOrganizationId$` emits and `userKey$` completes", () => { @@ -486,7 +486,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); }); diff --git a/libs/common/src/tools/rx.spec.ts b/libs/common/src/tools/rx.spec.ts index 9ce147a3ff4..2c433fef93b 100644 --- a/libs/common/src/tools/rx.spec.ts +++ b/libs/common/src/tools/rx.spec.ts @@ -56,7 +56,7 @@ describe("errorOnChange", () => { source$.complete(); - expect(complete).toBeTrue(); + expect(complete).toBe(true); }); it("errors when the input changes", async () => { diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 9fdb4327b98..cc3aa1946ca 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -77,7 +77,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folders$(mockUserId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: makeEncString("ENC_STRING_1") }, { id: "2", name: makeEncString("ENC_STRING_2") }, ]); @@ -98,7 +98,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folderViews$(mockUserId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: "DEC" }, { id: "2", name: "DEC" }, { name: "No Folder" }, diff --git a/package-lock.json b/package-lock.json index 87e0354f7ae..a8f82952517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,7 +151,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", - "jest-extended": "4.0.2", + "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", @@ -20729,28 +20729,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-extended": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz", - "integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-diff": "^29.0.0", - "jest-get-type": "^29.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "jest": ">=27.2.5" - }, - "peerDependenciesMeta": { - "jest": { - "optional": true - } - } - }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", diff --git a/package.json b/package.json index 641a49c4d04..0af1445a8ae 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", - "jest-extended": "4.0.2", + "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", From bbf128767172d5869c4e132b928d77406bc588a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 10:44:16 -0500 Subject: [PATCH 193/270] clean up classes that aren't tailwind-compatible (#12871) --- .../generator/components/src/catchall-settings.component.html | 2 +- .../components/src/credential-generator.component.html | 4 ++-- .../components/src/forwarder-settings.component.html | 2 +- .../components/src/passphrase-settings.component.html | 2 +- .../generator/components/src/password-settings.component.html | 2 +- .../components/src/subaddress-settings.component.html | 2 +- .../components/src/username-generator.component.html | 4 ++-- .../generator/components/src/username-settings.component.html | 2 +- .../send-list-items-container.component.html | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libs/tools/generator/components/src/catchall-settings.component.html b/libs/tools/generator/components/src/catchall-settings.component.html index 61037c91a73..4afa145c055 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.html +++ b/libs/tools/generator/components/src/catchall-settings.component.html @@ -1,4 +1,4 @@ -
    + {{ "domainName" | i18n }}
    - + {{ "type" | i18n }} -
    + {{ "service" | i18n }} + {{ "forwarderDomainName" | i18n }}
    {{ "options" | i18n }}
    - +
    diff --git a/libs/tools/generator/components/src/password-settings.component.html b/libs/tools/generator/components/src/password-settings.component.html index 9f8e00921fb..5e4d1079725 100644 --- a/libs/tools/generator/components/src/password-settings.component.html +++ b/libs/tools/generator/components/src/password-settings.component.html @@ -2,7 +2,7 @@

    {{ "options" | i18n }}

    - +
    diff --git a/libs/tools/generator/components/src/subaddress-settings.component.html b/libs/tools/generator/components/src/subaddress-settings.component.html index 1dfb5e3460d..b7f71b12b2a 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.html +++ b/libs/tools/generator/components/src/subaddress-settings.component.html @@ -1,4 +1,4 @@ - + {{ "email" | i18n }}
    - + {{ "type" | i18n }} -
    + {{ "service" | i18n }} + {{ send.name }} From f6f4bc9d4b68aeb25ba95b169e1066a97289b800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 10:45:14 -0500 Subject: [PATCH 194/270] remove circular reference to generator-core (#12869) --- libs/tools/generator/core/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/tools/generator/core/tsconfig.json b/libs/tools/generator/core/tsconfig.json index 7c703686b20..a95b588686f 100644 --- a/libs/tools/generator/core/tsconfig.json +++ b/libs/tools/generator/core/tsconfig.json @@ -5,7 +5,6 @@ "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], "@bitwarden/auth/common": ["../../../auth/src/common"], "@bitwarden/common/*": ["../../../common/src/*"], - "@bitwarden/generator-core": ["../../../tools/generator/core/src"], "@bitwarden/key-management": ["../../../key-management/src"] } }, From e79dab868956a21e1886f38ae8d1dbc05246ead9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 10:47:02 -0500 Subject: [PATCH 195/270] [PM-16789] introduce extension metadata (#12717) --- libs/common/src/tools/extension/data.ts | 26 + .../extension-registry.abstraction.ts | 104 ++ .../src/tools/extension/extension-site.ts | 20 + libs/common/src/tools/extension/factory.ts | 24 + libs/common/src/tools/extension/index.ts | 12 + libs/common/src/tools/extension/metadata.ts | 17 + .../runtime-extension-registry.spec.ts | 923 ++++++++++++++++++ .../extension/runtime-extension-registry.ts | 286 ++++++ libs/common/src/tools/extension/type.ts | 109 +++ .../src/tools/extension/vendor/addyio.ts | 25 + .../src/tools/extension/vendor/bitwarden.ts | 8 + .../common/src/tools/extension/vendor/data.ts | 11 + .../src/tools/extension/vendor/duckduckgo.ts | 25 + .../src/tools/extension/vendor/fastmail.ts | 25 + .../tools/extension/vendor/forwardemail.ts | 25 + .../src/tools/extension/vendor/index.ts | 30 + .../src/tools/extension/vendor/mozilla.ts | 26 + .../src/tools/extension/vendor/readme.md | 33 + .../src/tools/extension/vendor/simplelogin.ts | 25 + libs/common/src/tools/util.ts | 19 + 20 files changed, 1773 insertions(+) create mode 100644 libs/common/src/tools/extension/data.ts create mode 100644 libs/common/src/tools/extension/extension-registry.abstraction.ts create mode 100644 libs/common/src/tools/extension/extension-site.ts create mode 100644 libs/common/src/tools/extension/factory.ts create mode 100644 libs/common/src/tools/extension/index.ts create mode 100644 libs/common/src/tools/extension/metadata.ts create mode 100644 libs/common/src/tools/extension/runtime-extension-registry.spec.ts create mode 100644 libs/common/src/tools/extension/runtime-extension-registry.ts create mode 100644 libs/common/src/tools/extension/type.ts create mode 100644 libs/common/src/tools/extension/vendor/addyio.ts create mode 100644 libs/common/src/tools/extension/vendor/bitwarden.ts create mode 100644 libs/common/src/tools/extension/vendor/data.ts create mode 100644 libs/common/src/tools/extension/vendor/duckduckgo.ts create mode 100644 libs/common/src/tools/extension/vendor/fastmail.ts create mode 100644 libs/common/src/tools/extension/vendor/forwardemail.ts create mode 100644 libs/common/src/tools/extension/vendor/index.ts create mode 100644 libs/common/src/tools/extension/vendor/mozilla.ts create mode 100644 libs/common/src/tools/extension/vendor/readme.md create mode 100644 libs/common/src/tools/extension/vendor/simplelogin.ts create mode 100644 libs/common/src/tools/util.ts diff --git a/libs/common/src/tools/extension/data.ts b/libs/common/src/tools/extension/data.ts new file mode 100644 index 00000000000..cab6272a068 --- /dev/null +++ b/libs/common/src/tools/extension/data.ts @@ -0,0 +1,26 @@ +/** well-known name for a feature extensible through an extension. */ +export const Site = Object.freeze({ + forwarder: "forwarder", +} as const); + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export const Field = Object.freeze({ + token: "token", + baseUrl: "baseUrl", + domain: "domain", + prefix: "prefix", +} as const); + +/** Permission levels for metadata. */ +export const Permission = Object.freeze({ + /** unless a rule denies access, allow it. If a permission is `null` + * or `undefined` it should be treated as `Permission.default`. + */ + default: "default", + /** unless a rule allows access, deny it. */ + none: "none", + /** access is explicitly granted to use an extension. */ + allow: "allow", + /** access is explicitly prohibited for this extension. This rule overrides allow rules. */ + deny: "deny", +} as const); diff --git a/libs/common/src/tools/extension/extension-registry.abstraction.ts b/libs/common/src/tools/extension/extension-registry.abstraction.ts new file mode 100644 index 00000000000..7734c01ea50 --- /dev/null +++ b/libs/common/src/tools/extension/extension-registry.abstraction.ts @@ -0,0 +1,104 @@ +import { ExtensionSite } from "./extension-site"; +import { + ExtensionMetadata, + ExtensionSet, + ExtensionPermission, + SiteId, + SiteMetadata, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them. */ +export abstract class ExtensionRegistry { + /** Registers a site supporting extensibility. + * Each site may only be registered once. Calls after the first for + * the same SiteId have no effect. + * @param site identifies the site being extended + * @param meta configures the extension site + * @return self for method chaining. + * @remarks The registry initializes with a set of allowed sites and fields. + * `registerSite` drops a registration and trims its allowed fields to only + * those indicated in the allow list. + */ + abstract registerSite: (meta: SiteMetadata) => this; + + /** List all registered extension sites with their extension permission, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract sites: () => { site: SiteMetadata; permission?: ExtensionPermission }[]; + + /** Get a site's metadata + * @param site identifies a site registration + * @return the site's metadata or `undefined` if the site isn't registered. + */ + abstract site: (site: SiteId) => SiteMetadata | undefined; + + /** Registers a vendor providing an extension. + * Each vendor may only be registered once. Calls after the first for + * the same VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerVendor: (meta: VendorMetadata) => this; + + /** List all registered vendors with their permissions, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract vendors: () => { vendor: VendorMetadata; permission?: ExtensionPermission }[]; + + /** Get a vendor's metadata + * @param site identifies a vendor registration + * @return the vendor's metadata or `undefined` if the vendor isn't registered. + */ + abstract vendor: (vendor: VendorId) => VendorMetadata | undefined; + + /** Registers an extension provided by a vendor to an extension site. + * The vendor and site MUST be registered before the extension. + * Each extension may only be registered once. Calls after the first for + * the same SiteId and VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerExtension: (meta: ExtensionMetadata) => this; + + /** Get an extensions metadata + * @param site identifies the extension's site + * @param vendor identifies the extension's vendor + * @return the extension's metadata or `undefined` if the extension isn't registered. + */ + abstract extension: (site: SiteId, vendor: VendorId) => ExtensionMetadata | undefined; + + /** List all registered extensions and their permissions */ + abstract extensions: () => ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }>; + + /** Registers a permission. Only 1 permission can be registered for each extension set. + * Calls after the first *replace* the registered permission. + * @param set the collection of extensions affected by the permission + * @param permission the permission for the collection + * @return self for method chaining. + */ + abstract setPermission: (set: ExtensionSet, permission: ExtensionPermission) => this; + + /** Retrieves the current permission for the given extension set or `undefined` if + * a permission doesn't exist. + */ + abstract permission: (set: ExtensionSet) => ExtensionPermission | undefined; + + /** Returns all registered extension rules. */ + abstract permissions: () => { set: ExtensionSet; permission: ExtensionPermission }[]; + + /** Creates a point-in-time snapshot of the registry's contents with extension + * permissions applied for the provided SiteId. + * @param id identifies the extension site to create. + * @returns the extension site, or `undefined` if the site is not registered. + */ + abstract build: (id: SiteId) => ExtensionSite | undefined; +} diff --git a/libs/common/src/tools/extension/extension-site.ts b/libs/common/src/tools/extension/extension-site.ts new file mode 100644 index 00000000000..e8aba008493 --- /dev/null +++ b/libs/common/src/tools/extension/extension-site.ts @@ -0,0 +1,20 @@ +import { deepFreeze } from "../util"; + +import { ExtensionMetadata, SiteMetadata, VendorId } from "./type"; + +/** Describes the capabilities of an extension site. + * This type is immutable. + */ +export class ExtensionSite { + /** instantiate the extension site + * @param site describes the extension site + * @param vendors describes the available vendors + * @param extensions describes the available extensions + */ + constructor( + readonly site: Readonly, + readonly extensions: ReadonlyMap>, + ) { + deepFreeze(this); + } +} diff --git a/libs/common/src/tools/extension/factory.ts b/libs/common/src/tools/extension/factory.ts new file mode 100644 index 00000000000..10ebc77804a --- /dev/null +++ b/libs/common/src/tools/extension/factory.ts @@ -0,0 +1,24 @@ +import { DefaultFields, DefaultSites, Extension } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { VendorExtensions, Vendors } from "./vendor"; + +// FIXME: find a better way to build the registry than a hard-coded factory function + +/** Constructs the extension registry */ +export function buildExtensionRegistry() { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + for (const site of Reflect.ownKeys(Extension) as string[]) { + registry.registerSite(Extension[site]); + } + + for (const vendor of Vendors) { + registry.registerVendor(vendor); + } + + for (const extension of VendorExtensions) { + registry.registerExtension(extension); + } + + return registry; +} diff --git a/libs/common/src/tools/extension/index.ts b/libs/common/src/tools/extension/index.ts new file mode 100644 index 00000000000..e786dde4f59 --- /dev/null +++ b/libs/common/src/tools/extension/index.ts @@ -0,0 +1,12 @@ +export { Site, Field, Permission } from "./data"; +export { + SiteId, + FieldId, + VendorId, + ExtensionId, + ExtensionPermission, + SiteMetadata, + ExtensionMetadata, + VendorMetadata, +} from "./type"; +export { ExtensionSite } from "./extension-site"; diff --git a/libs/common/src/tools/extension/metadata.ts b/libs/common/src/tools/extension/metadata.ts new file mode 100644 index 00000000000..895b1d1b31f --- /dev/null +++ b/libs/common/src/tools/extension/metadata.ts @@ -0,0 +1,17 @@ +import { Field, Site, Permission } from "./data"; +import { FieldId, SiteId, SiteMetadata } from "./type"; + +export const DefaultSites: SiteId[] = Object.freeze(Object.keys(Site) as any); + +export const DefaultFields: FieldId[] = Object.freeze(Object.keys(Field) as any); + +export const Extension: Record = { + [Site.forwarder]: { + id: Site.forwarder, + availableFields: [Field.baseUrl, Field.domain, Field.prefix, Field.token], + }, +}; + +export const AllowedPermissions: ReadonlyArray = Object.freeze( + Object.values(Permission), +); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts new file mode 100644 index 00000000000..f4fe0e0ec05 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts @@ -0,0 +1,923 @@ +import { deepFreeze } from "../util"; + +import { Field, Site, Permission } from "./data"; +import { ExtensionSite } from "./extension-site"; +import { DefaultFields, DefaultSites } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { ExtensionMetadata, SiteId, SiteMetadata, VendorMetadata } from "./type"; +import { Bitwarden } from "./vendor/bitwarden"; + +// arbitrary test entities +const SomeSiteId: SiteId = Site.forwarder; + +const SomeSite: SiteMetadata = Object.freeze({ + id: SomeSiteId, + availableFields: [], +}); + +const SomeVendor = Bitwarden; +const SomeVendorId = SomeVendor.id; +const SomeExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: SomeVendor, name: "Some Product" }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://vault.bitwarden.com" }, + requestedFields: [], +}); + +const JustTrustUs: VendorMetadata = Object.freeze({ + id: "justrustus" as any, + name: "JustTrust.Us", +}); +const JustTrustUsExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: JustTrustUs }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://justrust.us" }, + requestedFields: [], +}); + +// In the following tests, not-null assertions (`!`) indicate that +// the returned object should never be null or undefined given +// the conditions defined within the test case +describe("RuntimeExtensionRegistry", () => { + describe("registerSite", () => { + it("registers an extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("interns the site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).not.toBe(SomeSite); + }); + + it("registers an extension site with fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const site: SiteMetadata = { + ...SomeSite, + availableFields: [Field.baseUrl], + }; + + const result = registry.registerSite(site).site(SomeSiteId); + + expect(result).toEqual(site); + }); + + it("ignores unavailable sites", () => { + const registry = new RuntimeExtensionRegistry([], []); + const ignored: SiteMetadata = { + id: "an-unavailable-site" as any, + availableFields: [], + }; + + const result = registry.registerSite(ignored).sites(); + + expect(result).toEqual([]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("ignores unknown available fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [SomeSite.availableFields, "ignored" as any], + }; + + const { availableFields } = registry.registerSite(ignored).site(SomeSiteId)!; + + expect(availableFields).toEqual(SomeSite.availableFields); + }); + + it("freezes the site definition", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const site = registry.registerSite(SomeSite).site(SomeSiteId)!; + + // reassigning `availableFields` throws b/c the object is frozen + expect(() => (site.availableFields = [Field.domain])).toThrow(); + }); + }); + + describe("site", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.site(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + + const first = registry.site(SomeSiteId); + const second = registry.site(SomeSiteId); + + expect(first).toBe(second); + }); + }); + + describe("sites", () => { + it("lists registered sites", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + const barSite: SiteMetadata = { + id: "bar" as any, + availableFields: [], + }; + + const result = registry.registerSite(SomeSite).registerSite(barSite).sites(); + + expect(result.some(({ site }) => site.id === SomeSiteId)).toBeTrue(); + expect(result.some(({ site }) => site.id === barSite.id)).toBeTrue(); + }); + + it("includes permissions for a site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).sites(); + + expect(result).toEqual([{ site: SomeSite }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .setPermission({ site: "bar" as any }, Permission.deny) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + }); + + describe("registerVendor", () => { + it("registers a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + const result = registry.registerVendor(SomeVendor).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor }]); + }); + + it("freezes the vendor definition", () => { + const registry = new RuntimeExtensionRegistry([], []); + // copy `SomeVendor` because it is already frozen + const original: VendorMetadata = { ...SomeVendor }; + + const [{ vendor }] = registry.registerVendor(original).vendors(); + + // reassigning `name` throws b/c the object is frozen + expect(() => (vendor.name = "Bytewarden")).toThrow(); + }); + }); + + describe("vendor", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry.vendor(SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor); + + const first = registry.vendor(SomeVendorId); + const second = registry.vendor(SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("vendors", () => { + it("lists registered vendors", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.registerVendor(SomeVendor).registerVendor(JustTrustUs); + + const result = registry.vendors(); + + expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBeTrue(); + expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBeTrue(); + }); + + it("includes permissions for a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry + .registerVendor(SomeVendor) + .setPermission({ vendor: SomeVendorId }, Permission.allow) + .vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry([], []); + const vendor: VendorMetadata = SomeVendor; + const ignored: VendorMetadata = { + ...SomeVendor, + name: "Duplicate", + }; + + const result = registry.registerVendor(vendor).registerVendor(ignored).vendors(); + + expect(result).toEqual([{ vendor }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.setPermission({ vendor: JustTrustUs.id }, Permission.deny).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + }); + + describe("setPermission", () => { + it("sets the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("ignores a site permission unless it is in the allowed sites list", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toBeUndefined(); + }); + + it("throws when a permission is invalid", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + expect(() => registry.setPermission({ all: true }, "invalid" as any)).toThrow(); + }); + + it("throws when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + expect(() => registry.setPermission(target, Permission.allow)).toThrow(); + }); + }); + + describe("permission", () => { + it("gets the default all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.default); + }); + + it("gets an all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + registry.setPermission(target, Permission.none); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.none); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("returns undefined when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + const permission = registry.permission(target); + + expect(permission).toBeUndefined(); + }); + }); + + describe("permissions", () => { + it("returns a default all permission by default", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.default }]); + }); + + it("returns the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.setPermission({ all: true }, Permission.none); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.none }]); + }); + + it("includes site permissions", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + registry.registerSite(SomeSite).setPermission({ site: SomeSiteId }, Permission.allow); + registry + .registerSite({ + id: "bar" as any, + availableFields: [], + }) + .setPermission({ site: "bar" as any }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.site === SomeSiteId && p.permission === Permission.allow), + ).toBeTrue(); + expect( + result.some((p: any) => p.set.site === "bar" && p.permission === Permission.deny), + ).toBeTrue(); + }); + + it("includes vendor permissions", () => { + const registry = new RuntimeExtensionRegistry([], DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + registry + .registerVendor(JustTrustUs) + .setPermission({ vendor: JustTrustUs.id }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.vendor === SomeVendorId && p.permission === Permission.allow), + ).toBeTrue(); + expect( + result.some( + (p: any) => p.set.vendor === JustTrustUs.id && p.permission === Permission.deny, + ), + ).toBeTrue(); + }); + }); + + describe("registerExtension", () => { + it("registers an extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("ignores extensions with nonregistered sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerVendor(SomeVendor); + + // precondition: the site is not registered + expect(registry.site(SomeSiteId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite); + + // precondition: the vendor is not registered + expect(registry.vendor(SomeVendorId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores repeated extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + // precondition: the vendor is already registered + expect(registry.extension(SomeSiteId, SomeVendorId)).toBeDefined(); + + const result = registry + .registerExtension({ + ...SomeExtension, + requestedFields: [Field.domain], + }) + .extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("interns site metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedSite = registry.site(SomeSiteId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.site).toBe(internedSite); + }); + + it("interns vendor metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedVendor = registry.vendor(SomeVendorId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.product.vendor).toBe(internedVendor); + }); + + it("freezes the extension metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const extension = registry.extension(SomeSiteId, SomeVendorId)!; + + // field assignments & mutation functions throw b/c the object is frozen + expect(() => ((extension.site as any) = SomeSite)).toThrow(); + expect(() => ((extension.product.vendor as any) = SomeVendor)).toThrow(); + expect(() => ((extension.product.name as any) = "SomeVendor")).toThrow(); + expect(() => ((extension.host as any) = {})).toThrow(); + expect(() => ((extension.host.selfHost as any) = {})).toThrow(); + expect(() => ((extension.host as any).authorization = "basic")).toThrow(); + expect(() => ((extension.host as any).baseUrl = "https://www.example.com")).toThrow(); + expect(() => ((extension.requestedFields as any) = [Field.baseUrl])).toThrow(); + expect(() => (extension.requestedFields as any).push(Field.baseUrl)).toThrow(); + }); + }); + + describe("extension", () => { + describe("extension", () => { + it("returns `undefined` for an unknown extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("interns the extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + const first = registry.extension(SomeSiteId, SomeVendorId); + const second = registry.extension(SomeSiteId, SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("extensions", () => { + it("lists registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + registry.registerVendor(SomeVendor).registerExtension(SomeExtension); + registry.registerVendor(JustTrustUs).registerExtension(JustTrustUsExtension); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === SomeVendorId, + ), + ).toBeTrue(); + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === JustTrustUs.id, + ), + ).toBeTrue(); + }); + + it("includes permissions for extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension, permissions }) => + extension.site.id === SomeSiteId && + extension.product.vendor.id === SomeVendorId && + permissions.includes(Permission.allow), + ), + ).toBeTrue(); + }); + }); + + describe("build", () => { + it("builds an empty extension site when no extensions are registered", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.build(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + describe("when the all permission is `default`", () => { + const allPermission = Permission.default; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `none`", () => { + const allPermission = Permission.none; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.none); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.allow]])("includes sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.allow]])("includes vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `allow`", () => { + const allPermission = Permission.allow; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + }); + + describe("when the all permission is `deny`", () => { + const allPermission = Permission.deny; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.deny); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + }); + }); +}); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.ts b/libs/common/src/tools/extension/runtime-extension-registry.ts new file mode 100644 index 00000000000..1c630dcc915 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.ts @@ -0,0 +1,286 @@ +import { deepFreeze } from "../util"; + +import { ExtensionRegistry } from "./extension-registry.abstraction"; +import { ExtensionSite } from "./extension-site"; +import { AllowedPermissions } from "./metadata"; +import { + ExtensionMetadata, + ExtensionPermission, + ExtensionSet, + FieldId, + ProductMetadata, + SiteMetadata, + SiteId, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them in application memory. */ +export class RuntimeExtensionRegistry implements ExtensionRegistry { + /** Instantiates the extension registry + * @param allowedSites sites that are valid for use by any extension; + * this is most useful to disable an extension site that is only + * available on a specific client. + * @param allowedFields fields that are valid for use by any extension; + * this is most useful to prohibit access to a field via policy. + */ + constructor( + private readonly allowedSites: SiteId[], + private readonly allowedFields: FieldId[], + ) { + Object.freeze(this.allowedFields); + Object.freeze(this.allowedSites); + } + + private allPermission: ExtensionPermission = "default"; + + private siteRegistrations = new Map(); + private sitePermissions = new Map(); + + private vendorRegistrations = new Map(); + private vendorPermissions = new Map(); + + private extensionRegistrations = new Array(); + private extensionsBySiteByVendor = new Map>(); + + registerSite(site: SiteMetadata): this { + if (!this.allowedSites.includes(site.id)) { + return this; + } + + // verify requested fields are on the list of valid fields to expose to + // an extension + const availableFields = site.availableFields.filter((field) => + this.allowedFields.includes(field), + ); + const validated: SiteMetadata = deepFreeze({ id: site.id, availableFields }); + + if (!this.siteRegistrations.has(site.id)) { + this.siteRegistrations.set(site.id, validated); + } + + return this; + } + + site(site: SiteId): SiteMetadata | undefined { + const result = this.siteRegistrations.get(site); + return result; + } + + sites() { + const sites: { site: SiteMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, site] of this.siteRegistrations.entries()) { + const s: (typeof sites)[number] = { site }; + const permission = this.sitePermissions.get(k); + if (permission) { + s.permission = permission; + } + + sites.push(s); + } + + return sites; + } + + registerVendor(vendor: VendorMetadata): this { + if (!this.vendorRegistrations.has(vendor.id)) { + const frozen = deepFreeze(vendor); + this.vendorRegistrations.set(vendor.id, frozen); + } + + return this; + } + + vendor(vendor: VendorId): VendorMetadata | undefined { + const result = this.vendorRegistrations.get(vendor); + return result; + } + + vendors() { + const vendors: { vendor: VendorMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, vendor] of this.vendorRegistrations.entries()) { + const s: (typeof vendors)[number] = { vendor }; + const permission = this.vendorPermissions.get(k); + if (permission) { + s.permission = permission; + } + + vendors.push(s); + } + + return vendors; + } + + setPermission(set: ExtensionSet, permission: ExtensionPermission): this { + if (!AllowedPermissions.includes(permission)) { + throw new Error(`invalid extension permission: ${permission}`); + } + + if ("all" in set && set.all) { + this.allPermission = permission; + } else if ("vendor" in set) { + this.vendorPermissions.set(set.vendor, permission); + } else if ("site" in set) { + if (this.allowedSites.includes(set.site)) { + this.sitePermissions.set(set.site, permission); + } + } else { + throw new Error(`Unrecognized extension set received: ${JSON.stringify(set)}.`); + } + + return this; + } + + permission(set: ExtensionSet) { + if ("all" in set && set.all) { + return this.allPermission; + } else if ("vendor" in set) { + return this.vendorPermissions.get(set.vendor); + } else if ("site" in set) { + return this.sitePermissions.get(set.site); + } else { + return undefined; + } + } + + permissions() { + const rules: { set: ExtensionSet; permission: ExtensionPermission }[] = []; + rules.push({ set: { all: true }, permission: this.allPermission }); + + for (const [site, permission] of this.sitePermissions.entries()) { + rules.push({ set: { site }, permission }); + } + + for (const [vendor, permission] of this.vendorPermissions.entries()) { + rules.push({ set: { vendor }, permission }); + } + + return rules; + } + + registerExtension(meta: ExtensionMetadata): this { + const site = this.siteRegistrations.get(meta.site.id); + const vendor = this.vendorRegistrations.get(meta.product.vendor.id); + if (!site || !vendor) { + return this; + } + + // exit early if the extension is already registered + const extensionsByVendor = + this.extensionsBySiteByVendor.get(meta.site.id) ?? new Map(); + if (extensionsByVendor.has(meta.product.vendor.id)) { + return this; + } + + // create immutable copy; this updates the vendor and site with + // their internalized representation to provide reference equality + // across registrations + const product: ProductMetadata = { vendor }; + if (meta.product.name) { + product.name = meta.product.name; + } + const extension: ExtensionMetadata = Object.freeze({ + site, + product: Object.freeze(product), + host: Object.freeze({ ...meta.host }), + requestedFields: Object.freeze([...meta.requestedFields]), + }); + + // register it + const index = this.extensionRegistrations.push(extension) - 1; + extensionsByVendor.set(vendor.id, index); + this.extensionsBySiteByVendor.set(site.id, extensionsByVendor); + + return this; + } + + extension(site: SiteId, vendor: VendorId): ExtensionMetadata | undefined { + const index = this.extensionsBySiteByVendor.get(site)?.get(vendor) ?? -1; + if (index < 0) { + return undefined; + } else { + return this.extensionRegistrations[index]; + } + } + + private getPermissions(site: SiteId, vendor: VendorId): ExtensionPermission[] { + const permissions = [ + this.sitePermissions.get(site), + this.vendorPermissions.get(vendor), + this.allPermission, + // Need to cast away `undefined` because typescript isn't + // aware that the filter eliminates undefined elements + ].filter((p) => !!p) as ExtensionPermission[]; + + return permissions; + } + + extensions(): ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }> { + const extensions = []; + for (const extension of this.extensionRegistrations) { + const permissions = this.getPermissions(extension.site.id, extension.product.vendor.id); + + extensions.push({ extension, permissions }); + } + + return extensions; + } + + build(id: SiteId): ExtensionSite | undefined { + const site = this.siteRegistrations.get(id); + if (!site) { + return undefined; + } + + if (this.allPermission === "deny") { + return new ExtensionSite(site, new Map()); + } + + const extensions = new Map(); + const entries = this.extensionsBySiteByVendor.get(id)?.entries() ?? ([] as const); + for (const [vendor, index] of entries) { + const permissions = this.getPermissions(id, vendor); + + const extension = evaluate(permissions, this.extensionRegistrations[index]); + if (extension) { + extensions.set(vendor, extension); + } + } + + const extensionSite = new ExtensionSite(site, extensions); + return extensionSite; + } +} + +function evaluate( + permissions: ExtensionPermission[], + value: ExtensionMetadata, +): ExtensionMetadata | undefined { + // deny always wins + if (permissions.includes("deny")) { + return undefined; + } + + // allow overrides implicit permissions + if (permissions.includes("allow")) { + return value; + } + + // none permission becomes a deny + if (permissions.includes("none")) { + return undefined; + } + + // default permission becomes an allow + if (permissions.includes("default")) { + return value; + } + + // if no permission is recognized, throw. This code is unreachable. + throw new Error("failed to recognize any permissions"); +} diff --git a/libs/common/src/tools/extension/type.ts b/libs/common/src/tools/extension/type.ts new file mode 100644 index 00000000000..f37d4ff8e53 --- /dev/null +++ b/libs/common/src/tools/extension/type.ts @@ -0,0 +1,109 @@ +import { Opaque } from "type-fest"; + +import { Site, Field, Permission } from "./data"; + +/** well-known name for a feature extensible through an extension. */ +export type SiteId = keyof typeof Site; + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export type FieldId = keyof typeof Field; + +/** Identifies a vendor extending bitwarden */ +export type VendorId = Opaque<"vendor", string>; + +/** uniquely identifies an extension. */ +export type ExtensionId = { site: SiteId; vendor: VendorId }; + +/** Permission levels for metadata. */ +export type ExtensionPermission = keyof typeof Permission; + +/** The capabilities and descriptive content for an extension */ +export type SiteMetadata = { + /** Uniquely identifies the extension site. */ + id: SiteId; + + /** Lists the fields disclosed by the extension to the vendor */ + availableFields: FieldId[]; +}; + +/** The capabilities and descriptive content for an extension */ +export type VendorMetadata = { + /** Uniquely identifies the vendor. */ + id: VendorId; + + /** Brand name of the service providing the extension. */ + name: string; +}; + +type TokenHeader = + | { + /** Transmit the token as the value of an `Authentication` header */ + authentication: true; + } + | { + /** Transmit the token as an `Authorization` header and a formatted value + * * `bearer` uses OAUTH-2.0 bearer token format + * * `token` prefixes the token with "Token" + * * `basic-username` uses HTTP Basic authentication format, encoding the + * token as the username. + */ + authorization: "bearer" | "token" | "basic-username"; + }; + +/** Catalogues an extension's hosting status. + * selfHost: "never" always uses the service's base URL + * selfHost: "maybe" allows the user to override the service's + * base URL with their own. + * selfHost: "always" requires a base URL. + */ +export type ApiHost = TokenHeader & + ( + | { selfHost: "never"; baseUrl: string } + | { selfHost: "maybe"; baseUrl: string } + | { selfHost: "always" } + ); + +/** Describes a branded product */ +export type ProductMetadata = { + /** The vendor providing the extension */ + vendor: VendorMetadata; + + /** The branded name of the product, if it varies from the Vendor name */ + name?: string; +}; + +/** Describes an extension provided by a vendor */ +export type ExtensionMetadata = { + /** The part of Bitwarden extended by the vendor's services */ + readonly site: Readonly; + + /** Product description */ + readonly product: Readonly; + + /** Hosting provider capabilities required by the extension */ + readonly host: Readonly; + + /** Lists the fields disclosed by the extension to the vendor. + * This should be a subset of the `availableFields` listed in + * the extension. + */ + readonly requestedFields: ReadonlyArray>; +}; + +/** Identifies a collection of extensions. + */ +export type ExtensionSet = + | { + /** A set of extensions sharing an extension point */ + site: SiteId; + } + | { + /** A set of extensions sharing a vendor */ + vendor: VendorId; + } + | { + /** The total set of extensions. This is used to set a categorical + * rule affecting all extensions. + */ + all: true; + }; diff --git a/libs/common/src/tools/extension/vendor/addyio.ts b/libs/common/src/tools/extension/vendor/addyio.ts new file mode 100644 index 00000000000..c33abd570ad --- /dev/null +++ b/libs/common/src/tools/extension/vendor/addyio.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const AddyIo: VendorMetadata = { + id: Vendor.addyio, + name: "Addy.io", +}; + +export const AddyIoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: AddyIo, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://app.addy.io", + }, + requestedFields: [Field.token, Field.baseUrl, Field.domain], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/bitwarden.ts b/libs/common/src/tools/extension/vendor/bitwarden.ts new file mode 100644 index 00000000000..7f659c2d07f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/bitwarden.ts @@ -0,0 +1,8 @@ +import { VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Bitwarden: VendorMetadata = Object.freeze({ + id: Vendor.bitwarden, + name: "Bitwarden", +}); diff --git a/libs/common/src/tools/extension/vendor/data.ts b/libs/common/src/tools/extension/vendor/data.ts new file mode 100644 index 00000000000..7f0802ef82f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/data.ts @@ -0,0 +1,11 @@ +import { VendorId } from "../type"; + +export const Vendor = Object.freeze({ + addyio: "addyio" as VendorId, + bitwarden: "bitwarden" as VendorId, // RESERVED + duckduckgo: "duckduckgo" as VendorId, + fastmail: "fastmail" as VendorId, + forwardemail: "forwardemail" as VendorId, + mozilla: "mozilla" as VendorId, + simplelogin: "simplelogin" as VendorId, +} as const); diff --git a/libs/common/src/tools/extension/vendor/duckduckgo.ts b/libs/common/src/tools/extension/vendor/duckduckgo.ts new file mode 100644 index 00000000000..ca4634192f5 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/duckduckgo.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const DuckDuckGo: VendorMetadata = { + id: Vendor.duckduckgo, + name: "DuckDuckGo", +}; + +export const DuckDuckGoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: DuckDuckGo, + }, + host: { + authorization: "bearer", + selfHost: "never", + baseUrl: "https://quack.duckduckgo.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/fastmail.ts b/libs/common/src/tools/extension/vendor/fastmail.ts new file mode 100644 index 00000000000..e6fb9ec16be --- /dev/null +++ b/libs/common/src/tools/extension/vendor/fastmail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Fastmail: VendorMetadata = { + id: Vendor.fastmail, + name: "Fastmail", +}; + +export const FastmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Fastmail, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://api.fastmail.com", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/forwardemail.ts b/libs/common/src/tools/extension/vendor/forwardemail.ts new file mode 100644 index 00000000000..4fbc8c139b1 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/forwardemail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const ForwardEmail: VendorMetadata = { + id: Vendor.forwardemail, + name: "Forward Email", +}; + +export const ForwardEmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: ForwardEmail, + }, + host: { + authorization: "basic-username", + selfHost: "never", + baseUrl: "https://api.forwardemail.net", + }, + requestedFields: [Field.domain, Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/index.ts b/libs/common/src/tools/extension/vendor/index.ts new file mode 100644 index 00000000000..3bac78c80db --- /dev/null +++ b/libs/common/src/tools/extension/vendor/index.ts @@ -0,0 +1,30 @@ +import { deepFreeze } from "../../util"; + +import { AddyIo, AddyIoExtensions } from "./addyio"; +import { Bitwarden } from "./bitwarden"; +import { DuckDuckGo, DuckDuckGoExtensions } from "./duckduckgo"; +import { Fastmail, FastmailExtensions } from "./fastmail"; +import { ForwardEmail, ForwardEmailExtensions } from "./forwardemail"; +import { Mozilla, MozillaExtensions } from "./mozilla"; +import { SimpleLogin, SimpleLoginExtensions } from "./simplelogin"; + +export const Vendors = deepFreeze([ + AddyIo, + Bitwarden, + DuckDuckGo, + Fastmail, + ForwardEmail, + Mozilla, + SimpleLogin, +]); + +export const VendorExtensions = deepFreeze( + [ + AddyIoExtensions, + DuckDuckGoExtensions, + FastmailExtensions, + ForwardEmailExtensions, + MozillaExtensions, + SimpleLoginExtensions, + ].flat(), +); diff --git a/libs/common/src/tools/extension/vendor/mozilla.ts b/libs/common/src/tools/extension/vendor/mozilla.ts new file mode 100644 index 00000000000..b02b97d8777 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/mozilla.ts @@ -0,0 +1,26 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Mozilla: VendorMetadata = { + id: Vendor.mozilla, + name: "Mozilla", +}; + +export const MozillaExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Mozilla, + name: "Firefox Relay", + }, + host: { + authorization: "token", + selfHost: "never", + baseUrl: "https://relay.firefox.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/readme.md b/libs/common/src/tools/extension/vendor/readme.md new file mode 100644 index 00000000000..507769edd4e --- /dev/null +++ b/libs/common/src/tools/extension/vendor/readme.md @@ -0,0 +1,33 @@ +# Vendors + +This folder contains vendor-specific logic that extends the +Bitwarden password manager. + +## Vendor IDs + +A vendor's ID is used to identify and trace the code provided by +a vendor across Bitwarden. There are a few rules that vendor ids +must follow: + +1. They should be human-readable. (No UUIDs.) +2. They may only contain lowercase ASCII characters and numbers. +3. They must retain backwards compatibility with prior versions. + +As such, any given ID may not not match the vendor's present +brand identity. Said branding may be stored in `VendorMetadata.name`. + +## Core files + +There are 4 vendor-independent files in this directory. + +- `data.ts` - core metadata used for system initialization +- `index.ts` - exports vendor metadata +- `README.md` - this file + +## Vendor definitions + +Each vendor should have one and only one definition, whose name +MUST match their `VendorId`. The vendor is free to use either a +single file (e.g. `bitwarden.ts`) or a folder containing multiple +files (e.g. `bitwarden/extension.ts`, `bitwarden/forwarder.ts`) to +host their files. diff --git a/libs/common/src/tools/extension/vendor/simplelogin.ts b/libs/common/src/tools/extension/vendor/simplelogin.ts new file mode 100644 index 00000000000..21ee969cebb --- /dev/null +++ b/libs/common/src/tools/extension/vendor/simplelogin.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const SimpleLogin: VendorMetadata = { + id: Vendor.simplelogin, + name: "SimpleLogin", +}; + +export const SimpleLoginExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: SimpleLogin, + }, + host: { + authentication: true, + selfHost: "maybe", + baseUrl: "https://app.simplelogin.io", + }, + requestedFields: [Field.baseUrl, Field.token, Field.domain], + }, +]; diff --git a/libs/common/src/tools/util.ts b/libs/common/src/tools/util.ts new file mode 100644 index 00000000000..9a3a14c1c83 --- /dev/null +++ b/libs/common/src/tools/util.ts @@ -0,0 +1,19 @@ +/** Recursively freeze an object's own keys + * @param value the value to freeze + * @returns `value` + * @remarks this function is derived from MDN's `deepFreeze`, which + * has been committed to the public domain. + */ +export function deepFreeze(value: T): Readonly { + const keys = Reflect.ownKeys(value) as (keyof T)[]; + + for (const key of keys) { + const own = value[key]; + + if ((own && typeof own === "object") || typeof own === "function") { + deepFreeze(own); + } + } + + return Object.freeze(value); +} From 1721552294548fce688548100f40eb4d63e67ad3 Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:53:30 -0500 Subject: [PATCH 196/270] Updated org plans component to not call for tax info on self hosted instances (#12888) --- .../app/billing/organizations/organization-plans.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 4592f8de894..edc29b16049 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -193,7 +193,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.billing = await this.organizationApiService.getBilling(this.organizationId); this.sub = await this.organizationApiService.getSubscription(this.organizationId); this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId); - } else { + } else if (!this.selfHosted) { this.taxInformation = await this.apiService.getTaxInfo(); } From b26ad6a173da33bfcdfce7f2edd2b65c31957953 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 15 Jan 2025 11:22:24 -0500 Subject: [PATCH 197/270] update the suggested items section header copy based on the blocked state of the current page (#12860) --- apps/browser/src/_locales/en/messages.json | 3 +++ .../autofill-vault-list-items.component.html | 2 +- .../autofill-vault-list-items.component.ts | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 51fb3a0a770..9848be6d5fa 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4007,6 +4007,9 @@ "passkeyRemoved": { "message": "Passkey removed" }, + "autofillSuggestions": { + "message": "Autofill suggestions" + }, "itemSuggestions": { "message": "Suggested items" }, 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 047d168ecbb..eae8e2cc980 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 @@ -1,7 +1,7 @@ = + this.vaultPopupAutofillService.currentTabIsOnBlocklist$; + constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupAutofillService: VaultPopupAutofillService, From 58bd44fa2fe8124944f31b1d474f27ebc2147489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 11:49:26 -0500 Subject: [PATCH 198/270] replace toBeTrue() with toBe(true) (#12893) --- .../runtime-extension-registry.spec.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts index f4fe0e0ec05..6aa7382db57 100644 --- a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts +++ b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts @@ -143,8 +143,8 @@ describe("RuntimeExtensionRegistry", () => { const result = registry.registerSite(SomeSite).registerSite(barSite).sites(); - expect(result.some(({ site }) => site.id === SomeSiteId)).toBeTrue(); - expect(result.some(({ site }) => site.id === barSite.id)).toBeTrue(); + expect(result.some(({ site }) => site.id === SomeSiteId)).toBe(true); + expect(result.some(({ site }) => site.id === barSite.id)).toBe(true); }); it("includes permissions for a site", () => { @@ -230,8 +230,8 @@ describe("RuntimeExtensionRegistry", () => { const result = registry.vendors(); - expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBeTrue(); - expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBeTrue(); + expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBe(true); + expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBe(true); }); it("includes permissions for a vendor", () => { @@ -411,10 +411,10 @@ describe("RuntimeExtensionRegistry", () => { expect( result.some((p: any) => p.set.site === SomeSiteId && p.permission === Permission.allow), - ).toBeTrue(); + ).toBe(true); expect( result.some((p: any) => p.set.site === "bar" && p.permission === Permission.deny), - ).toBeTrue(); + ).toBe(true); }); it("includes vendor permissions", () => { @@ -428,12 +428,12 @@ describe("RuntimeExtensionRegistry", () => { expect( result.some((p: any) => p.set.vendor === SomeVendorId && p.permission === Permission.allow), - ).toBeTrue(); + ).toBe(true); expect( result.some( (p: any) => p.set.vendor === JustTrustUs.id && p.permission === Permission.deny, ), - ).toBeTrue(); + ).toBe(true); }); }); @@ -561,13 +561,13 @@ describe("RuntimeExtensionRegistry", () => { ({ extension }) => extension.site.id === SomeSiteId && extension.product.vendor.id === SomeVendorId, ), - ).toBeTrue(); + ).toBe(true); expect( result.some( ({ extension }) => extension.site.id === SomeSiteId && extension.product.vendor.id === JustTrustUs.id, ), - ).toBeTrue(); + ).toBe(true); }); it("includes permissions for extensions", () => { @@ -587,7 +587,7 @@ describe("RuntimeExtensionRegistry", () => { extension.product.vendor.id === SomeVendorId && permissions.includes(Permission.allow), ), - ).toBeTrue(); + ).toBe(true); }); }); From a5dce0535433c5ff5afff3b025260be9b9cee883 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 15 Jan 2025 17:59:39 +0100 Subject: [PATCH 199/270] [PM-17035] Fix biometric unlock badge in mv2 (#12854) * Fix biometrics not working in firefox or windows * Remove logs * Update badge after biometric unlock * Add removal todo note * Remove debug logging * Fix type warnings * Fix userkey typing in background biometrics service * Simplify types for userkey in foreground-browser-biometrics and runtime.background.ts * Add process reload logging * Fix autoprompt not working when no process reload happened * Fix biometric unlock badge in mv2 * Fix instant reprompt on firefox lock * Remove biometrics autoprompt on firefox (#12856) --- .../auth/popup/settings/account-security.component.html | 2 +- .../src/auth/popup/settings/account-security.component.ts | 7 +++++++ apps/browser/src/background/main.background.ts | 1 + .../biometrics/background-browser-biometrics.service.ts | 6 ++++++ .../src/angular/lock/components/lock.component.ts | 7 ++++++- 5 files changed, 21 insertions(+), 2 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 0f2754b2bf2..8bc28c9754d 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -20,7 +20,7 @@ {{ biometricUnavailabilityReason }} - + Date: Wed, 15 Jan 2025 18:14:31 +0100 Subject: [PATCH 200/270] Remove unused lifetime (#12889) --- apps/desktop/desktop_native/core/src/password/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/desktop_native/core/src/password/windows.rs b/apps/desktop/desktop_native/core/src/password/windows.rs index 32300b9f81f..8b297fc33b7 100644 --- a/apps/desktop/desktop_native/core/src/password/windows.rs +++ b/apps/desktop/desktop_native/core/src/password/windows.rs @@ -13,7 +13,7 @@ use windows::{ const CRED_FLAGS_NONE: u32 = 0; -pub async fn get_password<'a>(service: &str, account: &str) -> Result { +pub async fn get_password(service: &str, account: &str) -> Result { let target_name = U16CString::from_str(target_name(service, account))?; let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); From 1916fdc87f0f0d30e0f49edd208765b373ab1109 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:23:49 -0500 Subject: [PATCH 201/270] Adjust handling of GH action dependencies for CI/CD partnership (#12818) --- .github/renovate.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index a1987ca038d..5de11388039 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,13 +4,9 @@ "enabledManagers": ["cargo", "github-actions", "npm"], "packageRules": [ { - "groupName": "gh minor", + "groupName": "github action dependencies", "matchManagers": ["github-actions"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "matchManagers": ["github-actions"], - "commitMessagePrefix": "[deps] BRE:" + "matchUpdateTypes": ["minor"] }, { "matchManagers": ["cargo"], From 334dd4cebe1ad20fa7f1b65c56f08266b67d5ed4 Mon Sep 17 00:00:00 2001 From: Merissa Weinstein Date: Wed, 15 Jan 2025 11:53:03 -0600 Subject: [PATCH 202/270] [PM-10429] remove onboarding module for the browser refresh (#12759) * remove onboarding module * revert package.json commit --- apps/browser/src/_locales/en/messages.json | 6 -- .../vault-ui-onboarding.component.ts | 79 ---------------- .../components/vault-v2/vault-v2.component.ts | 5 -- .../services/vault-ui-onboarding.service.ts | 89 ------------------- .../src/platform/state/state-definitions.ts | 1 - 5 files changed, 180 deletions(-) delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts delete mode 100644 apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 9848be6d5fa..51e1203673b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4589,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts deleted file mode 100644 index 20b39c5a88d..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - ButtonModule, - DialogModule, - DialogService, - IconModule, - svgIcon, -} from "@bitwarden/components"; - -const announcementIcon = svgIcon` - - - - - - - - - - - - - - - - -`; - -@Component({ - standalone: true, - selector: "app-vault-ui-onboarding", - template: ` - -
    - -
    - - {{ "bitwardenNewLook" | i18n }} - - - {{ "bitwardenNewLookDesc" | i18n }} - - - - - - -
    - `, - imports: [CommonModule, DialogModule, ButtonModule, JslibModule, IconModule], -}) -export class VaultUiOnboardingComponent { - icon = announcementIcon; - - static open(dialogService: DialogService) { - return dialogService.open(VaultUiOnboardingComponent); - } - - navigateToLink = async () => { - window.open( - "https://bitwarden.com/blog/bringing-intuitive-workflows-and-visual-updates-to-the-bitwarden-browser/", - "_blank", - ); - }; -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index a0c54987357..7c21c7e6a0c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -19,7 +19,6 @@ import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-he import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; import { VaultPopupItemsService } from "../../services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service"; -import { VaultUiOnboardingService } from "../../services/vault-ui-onboarding.service"; import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component"; import { @@ -58,7 +57,6 @@ enum VaultState { VaultHeaderV2Component, DecryptionFailureDialogComponent, ], - providers: [VaultUiOnboardingService], }) export class VaultV2Component implements OnInit, OnDestroy { cipherType = CipherType; @@ -93,7 +91,6 @@ export class VaultV2Component implements OnInit, OnDestroy { constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupListFiltersService: VaultPopupListFiltersService, - private vaultUiOnboardingService: VaultUiOnboardingService, private destroyRef: DestroyRef, private cipherService: CipherService, private dialogService: DialogService, @@ -123,8 +120,6 @@ export class VaultV2Component implements OnInit, OnDestroy { } async ngOnInit() { - await this.vaultUiOnboardingService.showOnboardingDialog(); - this.cipherService.failedToDecryptCiphers$ .pipe( map((ciphers) => ciphers.filter((c) => !c.isDeleted)), diff --git a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts b/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts deleted file mode 100644 index f50d6ebc236..00000000000 --- a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Injectable } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { - GlobalState, - KeyDefinition, - StateProvider, - VAULT_BROWSER_UI_ONBOARDING, -} from "@bitwarden/common/platform/state"; -import { DialogService } from "@bitwarden/components"; - -import { VaultUiOnboardingComponent } from "../components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component"; - -// Key definition for the Vault UI onboarding state. -// This key is used to store the state of the new UI information dialog. -export const GLOBAL_VAULT_UI_ONBOARDING = new KeyDefinition( - VAULT_BROWSER_UI_ONBOARDING, - "dialogState", - { - deserializer: (obj) => obj, - }, -); - -@Injectable() -export class VaultUiOnboardingService { - private onboardingUiReleaseDate = new Date("2024-12-10"); - - private vaultUiOnboardingState: GlobalState = this.stateProvider.getGlobal( - GLOBAL_VAULT_UI_ONBOARDING, - ); - - private readonly vaultUiOnboardingState$ = this.vaultUiOnboardingState.state$.pipe( - map((x) => x ?? false), - ); - - constructor( - private stateProvider: StateProvider, - private dialogService: DialogService, - private apiService: ApiService, - ) {} - - /** - * Checks whether the onboarding dialog should be shown and opens it if necessary. - * The dialog is shown if the user has not previously viewed it and is not a new account. - */ - async showOnboardingDialog(): Promise { - const hasViewedDialog = await this.getVaultUiOnboardingState(); - - if (!hasViewedDialog && !(await this.isNewAccount())) { - await this.openVaultUiOnboardingDialog(); - } - } - - private async openVaultUiOnboardingDialog(): Promise { - const dialogRef = VaultUiOnboardingComponent.open(this.dialogService); - - const result = firstValueFrom(dialogRef.closed); - - // Update the onboarding state when the dialog is closed - await this.setVaultUiOnboardingState(true); - - return result; - } - - private async isNewAccount(): Promise { - const userProfile = await this.apiService.getProfile(); - const profileCreationDate = new Date(userProfile.creationDate); - return profileCreationDate > this.onboardingUiReleaseDate; - } - - /** - * Updates and saves the state indicating whether the user has viewed - * the new UI onboarding information dialog. - */ - private async setVaultUiOnboardingState(value: boolean): Promise { - await this.vaultUiOnboardingState.update(() => value); - } - - /** - * Retrieves the current state indicating whether the user has viewed - * the new UI onboarding information dialog.s - */ - private async getVaultUiOnboardingState(): Promise { - return await firstValueFrom(this.vaultUiOnboardingState$); - } -} diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 1ed5227cb13..483a8c050d3 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -179,7 +179,6 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro web: "disk-local", }); export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk"); -export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk"); export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition( "newDeviceVerificationNotice", "disk", From 7b496cc7a57165452fe8f38e0f49a7190b9e4897 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:12:42 -0800 Subject: [PATCH 203/270] [deps] Vault: Update form-data to v4.0.1 (#11536) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: jaasen-livefront --- apps/cli/package.json | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 8a6dffb1cb3..d1d8ac76ec4 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -63,7 +63,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", - "form-data": "4.0.0", + "form-data": "4.0.1", "https-proxy-agent": "7.0.5", "inquirer": "8.2.6", "jsdom": "25.0.1", diff --git a/package-lock.json b/package-lock.json index a8f82952517..6fc9bebb61c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", - "form-data": "4.0.0", + "form-data": "4.0.1", "https-proxy-agent": "7.0.5", "inquirer": "8.2.6", "jquery": "3.7.1", @@ -205,7 +205,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", - "form-data": "4.0.0", + "form-data": "4.0.1", "https-proxy-agent": "7.0.5", "inquirer": "8.2.6", "jsdom": "25.0.1", @@ -17764,9 +17764,9 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", diff --git a/package.json b/package.json index 0af1445a8ae..9b7a2efefff 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", - "form-data": "4.0.0", + "form-data": "4.0.1", "https-proxy-agent": "7.0.5", "inquirer": "8.2.6", "jquery": "3.7.1", From 494d349b579d6dfc016b23289bdc7da1428684d1 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Wed, 15 Jan 2025 12:22:50 -0600 Subject: [PATCH 204/270] prevent bio prompt when switching to unlocked account (#12875) --- .../src/angular/lock/components/lock.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/key-management/src/angular/lock/components/lock.component.ts b/libs/key-management/src/angular/lock/components/lock.component.ts index 0adc9b838e9..c4e32078134 100644 --- a/libs/key-management/src/angular/lock/components/lock.component.ts +++ b/libs/key-management/src/angular/lock/components/lock.component.ts @@ -244,6 +244,10 @@ export class LockComponent implements OnInit, OnDestroy { if (activeAccount == null) { return; } + // this account may be unlocked, prevent any prompts so we can redirect to vault + if (await this.keyService.hasUserKeyInMemory(activeAccount.id)) { + return; + } this.setEmailAsPageSubtitle(activeAccount.email); From b0957cf1f6c8e6591c47e83d7c15b3f4f27da671 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:47:01 -0600 Subject: [PATCH 205/270] Remove setting setFingerprintValidated to true (#12874) Co-authored-by: Jake Fink --- .../biometrics/background-browser-biometrics.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 54b6faad84c..3031134dc34 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -98,7 +98,6 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; if (await this.keyService.validateUserKey(userKey, userId)) { await this.biometricStateService.setBiometricUnlockEnabled(true); - await this.biometricStateService.setFingerprintValidated(true); await this.keyService.setUserKey(userKey, userId); // to update badge and other things this.messagingService.send("switchAccount", { userId }); @@ -118,7 +117,6 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; if (await this.keyService.validateUserKey(userKey, userId)) { await this.biometricStateService.setBiometricUnlockEnabled(true); - await this.biometricStateService.setFingerprintValidated(true); await this.keyService.setUserKey(userKey, userId); // to update badge and other things this.messagingService.send("switchAccount", { userId }); From 99937e5831baa1ce9adbef4fd2403dcb5b7b777c Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Wed, 15 Jan 2025 14:13:03 -0500 Subject: [PATCH 206/270] PM-14051-storybook-implementation (#12840) * PM-14051 -initial storybook set up -Initial stories and folder structure * clean up typing on existing stories * add icons file * assign packages to autofill * row stories * row storiescd * -change file nnames to avoid rendering in main storybook instance - fix folder structure to set prep for doc creation * remove babel loader * -fix folder structure -add new package json -edit main to correct ts-config path * edit package name --- .github/renovate.json | 1 + .../content/components/.lit-storybook/main.ts | 67 +++++++++++++++++++ .../buttons/action-button.lit-stories.ts | 34 ++++++++++ .../buttons/badge-button.lit-stories.ts | 34 ++++++++++ .../buttons/close-button.lit-stories.ts | 29 ++++++++ .../buttons/edit-button.lit-stories.ts | 33 +++++++++ .../ciphers/cipher-action.lit-stories.ts | 36 ++++++++++ .../ciphers/cipher-icon.lit-stories.ts | 40 +++++++++++ .../cipher-indicator-icon.lit-stories.ts | 33 +++++++++ .../lit-stories/icons/icons.lit-stories.ts | 66 ++++++++++++++++++ .../notification/body.lit-stories.ts | 53 +++++++++++++++ .../notification/footer.lit-stories.ts | 32 +++++++++ .../notification/header.lit-stories.ts | 33 +++++++++ .../rows/action-row.lit-stories.ts | 31 +++++++++ .../rows/button-row.lit-stories.ts | 25 +++++++ .../lit-stories/rows/item-row.lit-stories.ts | 28 ++++++++ .../autofill/content/components/package.json | 7 ++ package-lock.json | 57 +++++++++++++++- package.json | 1 + 19 files changed, 637 insertions(+), 3 deletions(-) create mode 100644 apps/browser/src/autofill/content/components/.lit-storybook/main.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/buttons/badge-button.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/buttons/edit-button.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/rows/action-row.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/package.json diff --git a/.github/renovate.json b/.github/renovate.json index 5de11388039..350484b5c28 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -82,6 +82,7 @@ "prettier", "prettier-plugin-tailwindcss", "rimraf", + "@storybook/web-components-webpack5", "tabbable", "tldts", "wait-on" diff --git a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts new file mode 100644 index 00000000000..9e2da59d992 --- /dev/null +++ b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts @@ -0,0 +1,67 @@ +import { dirname, join } from "path"; +import path from "path"; +import type { StorybookConfig } from "@storybook/web-components-webpack5"; +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; +import remarkGfm from "remark-gfm"; + +const getAbsolutePath = (value: string): string => + dirname(require.resolve(join(value, "package.json"))); + +const config: StorybookConfig = { + stories: ["../lit-stories/**/*.lit-stories.@(js|jsx|ts|tsx)"], + addons: [ + getAbsolutePath("@storybook/addon-links"), + getAbsolutePath("@storybook/addon-essentials"), + getAbsolutePath("@storybook/addon-a11y"), + getAbsolutePath("@storybook/addon-designs"), + getAbsolutePath("@storybook/addon-interactions"), + { + name: "@storybook/addon-docs", + options: { + mdxPluginOptions: { + mdxCompileOptions: { + remarkPlugins: [remarkGfm], + }, + }, + }, + }, + ], + framework: { + name: getAbsolutePath("@storybook/web-components-webpack5"), + options: { + legacyRootApi: true, + }, + }, + core: { + disableTelemetry: true, + }, + env: (existingConfig) => ({ + ...existingConfig, + FLAGS: JSON.stringify({}), + }), + webpackFinal: async (config) => { + if (config.resolve) { + config.resolve.plugins = [ + new TsconfigPathsPlugin({ + configFile: path.resolve(__dirname, "../../../../../tsconfig.json"), + }), + ] as any; + } + + if (config.module && config.module.rules) { + config.module.rules.push({ + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + use: [ + { + loader: require.resolve("ts-loader"), + }, + ], + }); + } + return config; + }, + docs: {}, +}; + +export default config; diff --git a/apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts new file mode 100644 index 00000000000..aa53555d116 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/buttons/action-button.lit-stories.ts @@ -0,0 +1,34 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { ActionButton } from "../../buttons/action-button"; + +type Args = { + buttonText: string; + disabled: boolean; + theme: Theme; + buttonAction: (e: Event) => void; +}; + +export default { + title: "Components/Buttons/Action Button", + argTypes: { + buttonText: { control: "text" }, + disabled: { control: "boolean" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + buttonAction: { control: false }, + }, + args: { + buttonText: "Click Me", + disabled: false, + theme: ThemeTypes.Light, + buttonAction: () => alert("Clicked"), + }, +} as Meta; + +const Template = (args: Args) => ActionButton({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/buttons/badge-button.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/buttons/badge-button.lit-stories.ts new file mode 100644 index 00000000000..876a70eebc1 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/buttons/badge-button.lit-stories.ts @@ -0,0 +1,34 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { BadgeButton } from "../../buttons/badge-button"; + +type Args = { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}; + +export default { + title: "Components/Buttons/Badge Button", + argTypes: { + buttonText: { control: "text" }, + disabled: { control: "boolean" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + buttonAction: { control: false }, + }, + args: { + buttonText: "Click Me", + disabled: false, + theme: ThemeTypes.Light, + buttonAction: () => alert("Clicked"), + }, +} as Meta; + +const Template = (args: Args) => BadgeButton({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts new file mode 100644 index 00000000000..dc202f330ae --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts @@ -0,0 +1,29 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { CloseButton } from "../../buttons/close-button"; + +type Args = { + handleCloseNotification: (e: Event) => void; + theme: Theme; +}; +export default { + title: "Components/Buttons/Close Button", + argTypes: { + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + handleCloseNotification: { control: false }, + }, + args: { + theme: ThemeTypes.Light, + handleCloseNotification: () => { + alert("Close button clicked!"); + }, + }, +} as Meta; + +const Template = (args: Args) => CloseButton({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/buttons/edit-button.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/buttons/edit-button.lit-stories.ts new file mode 100644 index 00000000000..769fe475dd5 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/buttons/edit-button.lit-stories.ts @@ -0,0 +1,33 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { EditButton } from "../../buttons/edit-button"; + +type Args = { + buttonAction: (e: Event) => void; + buttonText: string; + disabled?: boolean; + theme: Theme; +}; +export default { + title: "Components/Buttons/Edit Button", + argTypes: { + buttonText: { control: "text" }, + disabled: { control: "boolean" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + buttonAction: { control: false }, + }, + args: { + buttonText: "Click Me", + disabled: false, + theme: ThemeTypes.Light, + buttonAction: () => alert("Clicked"), + }, +} as Meta; + +const Template = (args: Args) => EditButton({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts new file mode 100644 index 00000000000..e597cddabe6 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts @@ -0,0 +1,36 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; +import { CipherAction } from "../../cipher/cipher-action"; + +type Args = { + handleAction?: (e: Event) => void; + notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; + theme: Theme; +}; +export default { + title: "Components/Ciphers/Cipher Action", + argTypes: { + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + notificationType: { + control: "select", + options: [NotificationTypes.Change, NotificationTypes.Add], + }, + handleAction: { control: false }, + }, + args: { + theme: ThemeTypes.Light, + notificationType: NotificationTypes.Change, + handleAction: () => { + alert("Action triggered!"); + }, + }, +} as Meta; + +const Template = (args: Args) => CipherAction({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts new file mode 100644 index 00000000000..a8884f063de --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts @@ -0,0 +1,40 @@ +import { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { CipherIcon } from "../../cipher/cipher-icon"; + +type Args = { + color: string; + size: string; + theme: Theme; + uri?: string; +}; + +export default { + title: "Components/Ciphers/Cipher Icon", + argTypes: { + color: { control: "color" }, + size: { control: "text" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + uri: { control: "text" }, + }, + args: { + size: "50px", + theme: ThemeTypes.Light, + uri: "", + }, +} as Meta; + +const Template = (args: Args) => { + return html` +
    + ${CipherIcon({ ...args })} +
    + `; +}; + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts new file mode 100644 index 00000000000..2d031fa3afd --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icon.lit-stories.ts @@ -0,0 +1,33 @@ +import { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { CipherInfoIndicatorIcons } from "../../cipher/cipher-indicator-icons"; + +type Args = { + isBusinessOrg?: boolean; + isFamilyOrg?: boolean; + theme: Theme; +}; + +export default { + title: "Components/Ciphers/Cipher Indicator Icon", + argTypes: { + isBusinessOrg: { control: "boolean" }, + isFamilyOrg: { control: "boolean" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + }, + args: { + theme: ThemeTypes.Light, + isBusinessOrg: true, + isFamilyOrg: false, + }, +} as Meta; + +const Template: StoryObj["render"] = (args) => + html`
    ${CipherInfoIndicatorIcons({ ...args })}
    `; + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts new file mode 100644 index 00000000000..20c88a59246 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/icons/icons.lit-stories.ts @@ -0,0 +1,66 @@ +import { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import * as Icons from "../../icons"; + +type Args = { + color?: string; + disabled?: boolean; + theme: Theme; + size: number; + iconLink: URL; +}; + +export default { + title: "Components/Icons/Icons", + argTypes: { + iconLink: { control: "text" }, + color: { control: "color" }, + disabled: { control: "boolean" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + size: { control: "number", min: 10, max: 100, step: 1 }, + }, + args: { + iconLink: new URL("https://bitwarden.com"), + disabled: false, + theme: ThemeTypes.Light, + size: 50, + }, +} as Meta; + +const Template = (args: Args, IconComponent: (props: Args) => ReturnType) => html` +
    + ${IconComponent({ ...args })} +
    +`; + +const createIconStory = (iconName: keyof typeof Icons): StoryObj => { + const story = { + render: (args) => Template(args, Icons[iconName]), + } as StoryObj; + + if (iconName !== "BrandIconContainer") { + story.argTypes = { + iconLink: { table: { disable: true } }, + }; + } + + return story; +}; + +export const AngleDownIcon = createIconStory("AngleDown"); +export const BusinessIcon = createIconStory("Business"); +export const BrandIcon = createIconStory("BrandIconContainer"); +export const CloseIcon = createIconStory("Close"); +export const ExclamationTriangleIcon = createIconStory("ExclamationTriangle"); +export const FamilyIcon = createIconStory("Family"); +export const FolderIcon = createIconStory("Folder"); +export const GlobeIcon = createIconStory("Globe"); +export const PartyHornIcon = createIconStory("PartyHorn"); +export const PencilSquareIcon = createIconStory("PencilSquare"); +export const ShieldIcon = createIconStory("Shield"); +export const UserIcon = createIconStory("User"); diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts new file mode 100644 index 00000000000..00ea905a2f3 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts @@ -0,0 +1,53 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; + +import { NotificationType } from "../../../../notification/abstractions/notification-bar"; +import { CipherData } from "../../cipher/types"; +import { NotificationBody } from "../../notification/body"; + +type Args = { + ciphers: CipherData[]; + notificationType: NotificationType; + theme: Theme; +}; + +export default { + title: "Components/Notifications/Notification Body", + argTypes: { + ciphers: { control: "object" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + notificationType: { + control: "select", + options: ["add", "change", "unlock", "fileless-import"], + }, + }, + args: { + ciphers: [ + { + id: "1", + name: "Example Cipher", + type: CipherType.Login, + favorite: false, + reprompt: CipherRepromptType.None, + icon: { + imageEnabled: true, + image: "", + fallbackImage: "https://example.com/fallback.png", + icon: "icon-class", + }, + login: { username: "user@example.com", passkey: null }, + }, + ], + theme: ThemeTypes.Light, + notificationType: "change", + }, +} as Meta; + +const Template = (args: Args) => NotificationBody({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts new file mode 100644 index 00000000000..c8f30eb036f --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/footer.lit-stories.ts @@ -0,0 +1,32 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { NotificationType } from "../../../../notification/abstractions/notification-bar"; +import { NotificationFooter } from "../../notification/footer"; + +type Args = { + notificationType: NotificationType; + theme: Theme; +}; + +export default { + title: "Components/Notifications/Notification Footer", + argTypes: { + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + notificationType: { + control: "select", + options: ["add", "change", "unlock", "fileless-import"], + }, + }, + args: { + theme: ThemeTypes.Light, + notificationType: "add", + }, +} as Meta; + +const Template = (args: Args) => NotificationFooter({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts new file mode 100644 index 00000000000..fd8423f995e --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/header.lit-stories.ts @@ -0,0 +1,33 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { NotificationHeader } from "../../notification/header"; + +type Args = { + message: string; + standalone: boolean; + theme: Theme; + handleCloseNotification: (e: Event) => void; +}; + +export default { + title: "Components/Notifications/Notification Header", + argTypes: { + message: { control: "text" }, + standalone: { control: "boolean" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + }, + args: { + message: "This is a notification message", + standalone: true, + theme: ThemeTypes.Light, + handleCloseNotification: () => alert("Close Clicked"), + }, +} as Meta; + +const Template = (args: Args) => NotificationHeader({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/action-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/action-row.lit-stories.ts new file mode 100644 index 00000000000..4b100764205 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/rows/action-row.lit-stories.ts @@ -0,0 +1,31 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { ActionRow } from "../../rows/action-row"; + +type Args = { + itemText: string; + handleAction: (e: Event) => void; + theme: Theme; +}; + +export default { + title: "Components/Rows/Action Row", + argTypes: { + itemText: { control: "text" }, + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + handleAction: { control: false }, + }, + args: { + itemText: "Action Item", + theme: ThemeTypes.Light, + handleAction: () => alert("Action triggered"), + }, +} as Meta; + +const Template = (args: Args) => ActionRow({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts new file mode 100644 index 00000000000..3283c2798a3 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts @@ -0,0 +1,25 @@ +import { Meta, StoryObj } from "@storybook/web-components"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { ButtonRow } from "../../rows/button-row"; + +type Args = { + theme: Theme; +}; + +export default { + title: "Components/Rows/Button Row", + argTypes: { + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + }, + args: { + theme: ThemeTypes.Light, + }, +} as Meta; + +const Template = (args: Args) => ButtonRow({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts new file mode 100644 index 00000000000..fbb65201986 --- /dev/null +++ b/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts @@ -0,0 +1,28 @@ +import { Meta, StoryObj } from "@storybook/web-components"; +import { TemplateResult } from "lit"; + +import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; + +import { ItemRow } from "../../rows/item-row"; + +type Args = { + theme: Theme; + children: TemplateResult | TemplateResult[]; +}; + +export default { + title: "Components/Rows/Item Row", + argTypes: { + theme: { control: "select", options: [...Object.values(ThemeTypes)] }, + children: { control: "object" }, + }, + args: { + theme: ThemeTypes.Light, + }, +} as Meta; + +const Template = (args: Args) => ItemRow({ ...args }); + +export const Default: StoryObj = { + render: Template, +}; diff --git a/apps/browser/src/autofill/content/components/package.json b/apps/browser/src/autofill/content/components/package.json new file mode 100644 index 00000000000..8dbe9e7f516 --- /dev/null +++ b/apps/browser/src/autofill/content/components/package.json @@ -0,0 +1,7 @@ +{ + "name": "@bitwarden/lit-components", + "version": "2025.1.1", + "scripts": { + "storybook:lit": "storybook dev -p 6006 -c ./.lit-storybook" + } +} diff --git a/package-lock.json b/package-lock.json index 6fc9bebb61c..9544eb398a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,6 +98,7 @@ "@storybook/angular": "8.4.7", "@storybook/manager-api": "8.4.7", "@storybook/theming": "8.4.7", + "@storybook/web-components-webpack5": "8.4.7", "@types/argon2-browser": "1.18.4", "@types/chrome": "0.0.280", "@types/firefox-webext-browser": "120.0.4", @@ -2713,9 +2714,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -9017,6 +9018,56 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, + "node_modules/@storybook/web-components": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-8.4.7.tgz", + "integrity": "sha512-zR/bUWGkS5uxvqfXnW082ScrC4y5UrTdE1VKasezLGi5bTLub2hz8JP87PJgtWrq+mdrdmkLGzv5O4iJ/tlMAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/components": "8.4.7", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "8.4.7", + "@storybook/preview-api": "8.4.7", + "@storybook/theming": "8.4.7", + "tiny-invariant": "^1.3.1", + "ts-dedent": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "lit": "^2.0.0 || ^3.0.0", + "storybook": "^8.4.7" + } + }, + "node_modules/@storybook/web-components-webpack5": { + "version": "8.4.7", + "resolved": "https://registry.npmjs.org/@storybook/web-components-webpack5/-/web-components-webpack5-8.4.7.tgz", + "integrity": "sha512-RgLFQB7F4FOX5nOK3byaCo5Gs8nKMq1uNswOXdHSgZKfJfaZxmyMMGmnVUmOOLECsxyREokHwRDKma8SgFrRRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/builder-webpack5": "8.4.7", + "@storybook/web-components": "8.4.7", + "@types/node": "^22.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "lit": "^2.0.0 || ^3.0.0", + "storybook": "^8.4.7" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", diff --git a/package.json b/package.json index 9b7a2efefff..03d1f3d3c75 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@storybook/angular": "8.4.7", "@storybook/manager-api": "8.4.7", "@storybook/theming": "8.4.7", + "@storybook/web-components-webpack5": "8.4.7", "@types/argon2-browser": "1.18.4", "@types/chrome": "0.0.280", "@types/firefox-webext-browser": "120.0.4", From b750b6c082f45592a76ea7cf0ad06ff0ce6c961c Mon Sep 17 00:00:00 2001 From: Github Actions Date: Wed, 15 Jan 2025 22:10:26 +0000 Subject: [PATCH 207/270] Bumped client version(s) --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 8f6c6525a39..aff1d0ffbb0 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.2", + "version": "2025.1.3", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index d878e1af2aa..e825bd41581 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.2", + "version": "2025.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.2", + "version": "2025.1.3", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 08bdd745063..6feed970798 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.2", + "version": "2025.1.3", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 9544eb398a5..ae473f3e5f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -232,7 +232,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.1.2", + "version": "2025.1.3", "hasInstallScript": true, "license": "GPL-3.0" }, From ffa5afb5e81bc4a231c45c5ead94ee1514b6762d Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:30:01 -0500 Subject: [PATCH 208/270] Renamed group for consistency with server renovate (#12896) --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 350484b5c28..150ac1ac99d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,7 +4,7 @@ "enabledManagers": ["cargo", "github-actions", "npm"], "packageRules": [ { - "groupName": "github action dependencies", + "groupName": "github-action minor", "matchManagers": ["github-actions"], "matchUpdateTypes": ["minor"] }, From e4e436b76872d2f39ee66afc8b670c3964467e91 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:12:29 +1000 Subject: [PATCH 209/270] [PM-15182] Remove remove-provider-export-permission feature flag (#12878) * Remove remove-provider-export feature flag * Remove ts-strict comment * Revert changes to tests --- .../layouts/organization-layout.component.ts | 5 +-- .../organization-settings-routing.module.ts | 33 +++---------------- .../navigation-switcher.stories.ts | 3 -- .../product-switcher.stories.ts | 3 -- .../shared/product-switcher.service.spec.ts | 3 -- .../organization.service.abstraction.ts | 2 +- .../vnext.organization.service.ts | 2 +- .../models/domain/organization.ts | 6 +--- libs/common/src/enums/feature-flag.enum.ts | 2 -- 9 files changed, 9 insertions(+), 50 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 0b024817edc..c1112c51e39 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -77,10 +77,7 @@ export class OrganizationLayoutComponent implements OnInit { filter((org) => org != null), ); - this.canAccessExport$ = combineLatest([ - this.organization$, - this.configService.getFeatureFlag$(FeatureFlag.PM11360RemoveProviderExportPermission), - ]).pipe(map(([org, removeProviderExport]) => org.canAccessExport(removeProviderExport))); + this.canAccessExport$ = this.organization$.pipe(map((org) => org.canAccessExport)); this.showPaymentAndHistory$ = this.organization$.pipe( map( diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts index ac2c7448b0a..06ceaa0d9c7 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts @@ -1,13 +1,8 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { inject, NgModule } from "@angular/core"; -import { CanMatchFn, RouterModule, Routes } from "@angular/router"; -import { map } from "rxjs"; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { organizationPermissionsGuard } from "../../organizations/guards/org-permissions.guard"; import { organizationRedirectGuard } from "../../organizations/guards/org-redirect.guard"; @@ -16,11 +11,6 @@ import { PoliciesComponent } from "../../organizations/policies"; import { AccountComponent } from "./account.component"; import { TwoFactorSetupComponent } from "./two-factor-setup.component"; -const removeProviderExportPermission$: CanMatchFn = () => - inject(ConfigService) - .getFeatureFlag$(FeatureFlag.PM11360RemoveProviderExportPermission) - .pipe(map((removeProviderExport) => removeProviderExport === true)); - const routes: Routes = [ { path: "", @@ -68,27 +58,13 @@ const routes: Routes = [ titleId: "importData", }, }, - - // Export routing is temporarily duplicated to set the flag value passed into org.canAccessExport { path: "export", loadComponent: () => import("../tools/vault-export/org-vault-export.component").then( (mod) => mod.OrganizationVaultExportComponent, ), - canMatch: [removeProviderExportPermission$], // if this matches, the flag is ON - canActivate: [organizationPermissionsGuard((org) => org.canAccessExport(true))], - data: { - titleId: "exportVault", - }, - }, - { - path: "export", - loadComponent: () => - import("../tools/vault-export/org-vault-export.component").then( - (mod) => mod.OrganizationVaultExportComponent, - ), - canActivate: [organizationPermissionsGuard((org) => org.canAccessExport(false))], + canActivate: [organizationPermissionsGuard((org) => org.canAccessExport)], data: { titleId: "exportVault", }, @@ -118,7 +94,8 @@ function getSettingsRoute(organization: Organization) { if (organization.canManageDeviceApprovals) { return "device-approvals"; } - return undefined; + + return "/"; } @NgModule({ diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index a7ff50b4264..1c15f7cc7c1 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -157,7 +157,6 @@ export const SMAvailable: Story = { canManageUsers: false, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -173,7 +172,6 @@ export const SMAndACAvailable: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -189,7 +187,6 @@ export const WithAllOptions: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index b53d0243f64..7a4df4bad00 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -176,7 +176,6 @@ export const WithSM: Story = { canManageUsers: false, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -192,7 +191,6 @@ export const WithSMAndAC: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -208,7 +206,6 @@ export const WithAllOptions: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index a071d0f8852..919b3be0424 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -116,7 +116,6 @@ describe("ProductSwitcherService", () => { id: "1234", canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => true, }, ] as Organization[]); @@ -232,14 +231,12 @@ describe("ProductSwitcherService", () => { canAccessSecretsManager: true, enabled: true, name: "Org 2", - canAccessExport: (_) => true, }, { id: "4243", canAccessSecretsManager: true, enabled: true, name: "Org 32", - canAccessExport: (_) => true, }, ] as Organization[]); diff --git a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts index 2161feb516e..da81f340fda 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts @@ -17,7 +17,7 @@ export function canAccessSettingsTab(org: Organization): boolean { org.canManageSso || org.canManageScim || org.canAccessImport || - org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway + org.canAccessExport || org.canManageDeviceApprovals ); } diff --git a/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts b/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts index b5c0f6291fc..c25a153a068 100644 --- a/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts +++ b/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts @@ -17,7 +17,7 @@ export function canAccessSettingsTab(org: Organization): boolean { org.canManageSso || org.canManageScim || org.canAccessImport || - org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway + org.canAccessExport || org.canManageDeviceApprovals ); } diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 8441298bbff..9dcc9f0752c 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -182,11 +182,7 @@ export class Organization { ); } - canAccessExport(removeProviderExport: boolean) { - if (!removeProviderExport && this.isProviderUser) { - return true; - } - + get canAccessExport() { return ( this.isMember && (this.type === OrganizationUserType.Owner || diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index dde31acb9e3..d008a09d66c 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -44,7 +44,6 @@ export enum FeatureFlag { NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", MacOsNativeCredentialSync = "macos-native-credential-sync", - PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", PrivateKeyRegeneration = "pm-12241-private-key-regeneration", ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs", @@ -102,7 +101,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, - [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, [FeatureFlag.PM12443RemovePagingLogic]: FALSE, [FeatureFlag.PrivateKeyRegeneration]: FALSE, [FeatureFlag.ResellerManagedOrgAlert]: FALSE, From ca420d73143987c447e79fc9d091b744cc9b25fd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 16 Jan 2025 11:02:16 +0100 Subject: [PATCH 210/270] Attempt to fix snap build (#12882) * Attempt to fix snap build * Move snap * Add debug logging * Fix move * Remove debug logs --- apps/desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index aff1d0ffbb0..eaa27c3eb9f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -35,7 +35,7 @@ "clean:dist": "rimraf ./dist", "pack:dir": "npm run clean:dist && electron-builder --dir -p never", "pack:lin:flatpak": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --repo=build/.repo build/.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ./build/.repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", - "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && mksquashfs ./dist/tmp-snap/ $SNAP_FILE -noappend -comp lzo -no-fragments && rm -rf ./dist/tmp-snap/", + "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snapcraft pack ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/", "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", "pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never", From 68e02bc236e33c3e558f197439c2bb1f22940597 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:45:01 +0100 Subject: [PATCH 211/270] [deps] SM: Update eslint-plugin-tailwindcss to v3.17.5 (#11535) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- 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 ae473f3e5f9..ac31cc82586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,7 +147,7 @@ "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.8.0", - "eslint-plugin-tailwindcss": "3.17.4", + "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", @@ -16540,9 +16540,9 @@ } }, "node_modules/eslint-plugin-tailwindcss": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.17.4.tgz", - "integrity": "sha512-gJAEHmCq2XFfUP/+vwEfEJ9igrPeZFg+skeMtsxquSQdxba9XRk5bn0Bp9jxG1VV9/wwPKi1g3ZjItu6MIjhNg==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.17.5.tgz", + "integrity": "sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 03d1f3d3c75..cd4bc770790 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.8.0", - "eslint-plugin-tailwindcss": "3.17.4", + "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", From 5ba5f04e72c20be4384a508f15264d0bb1cb7e49 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:45:10 +0100 Subject: [PATCH 212/270] [deps] SM: Update husky to v9.1.7 (#10846) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- 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 ac31cc82586..11625f0fd9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,7 +151,7 @@ "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", - "husky": "9.1.4", + "husky": "9.1.7", "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", @@ -18972,9 +18972,9 @@ } }, "node_modules/husky": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", - "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index cd4bc770790..02ffd22a198 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", - "husky": "9.1.4", + "husky": "9.1.7", "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", From 51717cab07bfddf4be2eb6e80d28650a3fda865a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:45:20 +0100 Subject: [PATCH 213/270] [deps] SM: Update eslint to v8.57.1 (#11317) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 26 +++++++++++++------------- package.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11625f0fd9b..b94ad3b762f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -140,7 +140,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.3.9", - "eslint": "8.57.0", + "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", @@ -5905,9 +5905,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", "engines": { @@ -6013,14 +6013,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -16091,9 +16091,9 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", @@ -16101,8 +16101,8 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", diff --git a/package.json b/package.json index 02ffd22a198..07e3f217867 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.3.9", - "eslint": "8.57.0", + "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", From ad8694b641c31640637b139d288c28145ad8b368 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Thu, 16 Jan 2025 08:47:36 -0600 Subject: [PATCH 214/270] PM-15070 Star critical apps (#12109) Ability to star a record when flagged as critical. This is still behind a feature flag --- .../risk-insights/models/password-health.ts | 4 + .../critical-apps-api.service.spec.ts | 79 +++++++++ .../services/critical-apps-api.service.ts | 39 +++++ .../services/critical-apps.service.spec.ts | 142 ++++++++++++++++ .../services/critical-apps.service.ts | 159 ++++++++++++++++++ .../reports/risk-insights/services/index.ts | 2 + .../access-intelligence.module.ts | 15 ++ .../all-applications.component.html | 8 +- .../all-applications.component.ts | 102 ++++++----- .../application-table.mock.ts | 6 + .../risk-insights.component.html | 2 +- .../risk-insights.component.ts | 13 +- 12 files changed, 523 insertions(+), 48 deletions(-) create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index 94dad65fdc9..947fc8a79d3 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -30,6 +30,10 @@ export type ApplicationHealthReportDetail = { atRiskMemberDetails: MemberDetailsFlat[]; }; +export type ApplicationHealthReportDetailWithCriticalFlag = ApplicationHealthReportDetail & { + isMarkedAsCritical: boolean; +}; + /** * Breaks the cipher health info out by uri and passes * along the password health and member info diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts new file mode 100644 index 00000000000..838dc2c8241 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts @@ -0,0 +1,79 @@ +import { mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; +import { + PasswordHealthReportApplicationId, + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "./critical-apps.service"; + +describe("CriticalAppsApiService", () => { + let service: CriticalAppsApiService; + const apiService = mock(); + + beforeEach(() => { + service = new CriticalAppsApiService(apiService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should call apiService.send with correct parameters for SaveCriticalApps", (done) => { + const requests: PasswordHealthReportApplicationsRequest[] = [ + { organizationId: "org1" as OrganizationId, url: "test one" }, + { organizationId: "org1" as OrganizationId, url: "test two" }, + ]; + const response: PasswordHealthReportApplicationsResponse[] = [ + { + id: "1" as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "test one", + }, + { + id: "2" as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "test two", + }, + ]; + + apiService.send.mockReturnValue(Promise.resolve(response)); + + service.saveCriticalApps(requests).subscribe((result) => { + expect(result).toEqual(response); + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/reports/password-health-report-applications/", + requests, + true, + true, + ); + done(); + }); + }); + + it("should call apiService.send with correct parameters for GetCriticalApps", (done) => { + const orgId: OrganizationId = "org1" as OrganizationId; + const response: PasswordHealthReportApplicationsResponse[] = [ + { id: "1" as PasswordHealthReportApplicationId, organizationId: orgId, uri: "test one" }, + { id: "2" as PasswordHealthReportApplicationId, organizationId: orgId, uri: "test two" }, + ]; + + apiService.send.mockReturnValue(Promise.resolve(response)); + + service.getCriticalApps(orgId).subscribe((result) => { + expect(result).toEqual(response); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/reports/password-health-report-applications/${orgId.toString()}`, + null, + true, + true, + ); + done(); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts new file mode 100644 index 00000000000..edd2cf34b56 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts @@ -0,0 +1,39 @@ +import { from, Observable } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "./critical-apps.service"; + +export class CriticalAppsApiService { + constructor(private apiService: ApiService) {} + + saveCriticalApps( + requests: PasswordHealthReportApplicationsRequest[], + ): Observable { + const dbResponse = this.apiService.send( + "POST", + "/reports/password-health-report-applications/", + requests, + true, + true, + ); + + return from(dbResponse as Promise); + } + + getCriticalApps(orgId: OrganizationId): Observable { + const dbResponse = this.apiService.send( + "GET", + `/reports/password-health-report-applications/${orgId.toString()}`, + null, + true, + true, + ); + + return from(dbResponse as Promise); + } +} diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts new file mode 100644 index 00000000000..c6c4562310e --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts @@ -0,0 +1,142 @@ +import { randomUUID } from "crypto"; + +import { fakeAsync, flush } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; +import { + CriticalAppsService, + PasswordHealthReportApplicationId, + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "./critical-apps.service"; + +describe("CriticalAppsService", () => { + let service: CriticalAppsService; + const keyService = mock(); + const encryptService = mock(); + const criticalAppsApiService = mock({ + saveCriticalApps: jest.fn(), + getCriticalApps: jest.fn(), + }); + + beforeEach(() => { + service = new CriticalAppsService(keyService, encryptService, criticalAppsApiService); + + // reset mocks + jest.resetAllMocks(); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should set critical apps", async () => { + // arrange + const criticalApps = ["https://example.com", "https://example.org"]; + + const request = [ + { organizationId: "org1", url: "encryptedUrlName" }, + { organizationId: "org1", url: "encryptedUrlName" }, + ] as PasswordHealthReportApplicationsRequest[]; + + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); + + // act + await service.setCriticalApps("org1", criticalApps); + + // expectations + expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); + expect(encryptService.encrypt).toHaveBeenCalledTimes(2); + expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); + }); + + it("should exclude records that already exist", async () => { + // arrange + // one record already exists + service.setAppsInListForOrg([ + { + id: randomUUID() as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "https://example.com", + }, + ]); + + // two records are selected - one already in the database + const selectedUrls = ["https://example.com", "https://example.org"]; + + // expect only one record to be sent to the server + const request = [ + { organizationId: "org1", url: "encryptedUrlName" }, + ] as PasswordHealthReportApplicationsRequest[]; + + // mocked response + const response = [ + { id: "id1", organizationId: "org1", uri: "test" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); + + // act + await service.setCriticalApps("org1", selectedUrls); + + // expectations + expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); + expect(encryptService.encrypt).toHaveBeenCalledTimes(1); + expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); + }); + + it("should get critical apps", fakeAsync(() => { + const orgId = "org1" as OrganizationId; + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.decryptToUtf8.mockResolvedValue("https://example.com"); + criticalAppsApiService.getCriticalApps.mockReturnValue(of(response)); + + const mockRandomBytes = new Uint8Array(64) as CsprngArray; + const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey; + keyService.getOrgKey.mockResolvedValue(mockOrgKey); + + service.setOrganizationId(orgId as OrganizationId); + flush(); + + expect(keyService.getOrgKey).toHaveBeenCalledWith(orgId.toString()); + expect(encryptService.decryptToUtf8).toHaveBeenCalledTimes(2); + expect(criticalAppsApiService.getCriticalApps).toHaveBeenCalledWith(orgId); + })); + + it("should get by org id", () => { + const orgId = "org1" as OrganizationId; + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + { id: "id3", organizationId: "org2", uri: "https://example.org" }, + { id: "id4", organizationId: "org2", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + service.setAppsInListForOrg(response); + + service.getAppsListForOrg(orgId as OrganizationId).subscribe((res) => { + expect(res).toHaveLength(2); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts new file mode 100644 index 00000000000..10b7d3f1fbb --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts @@ -0,0 +1,159 @@ +import { + BehaviorSubject, + first, + firstValueFrom, + forkJoin, + from, + map, + Observable, + of, + Subject, + switchMap, + takeUntil, + zip, +} from "rxjs"; +import { Opaque } from "type-fest"; + +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; + +/* Retrieves and decrypts critical apps for a given organization + * Encrypts and saves data for a given organization + */ +export class CriticalAppsService { + private orgId = new BehaviorSubject(null); + private criticalAppsList = new BehaviorSubject([]); + private teardown = new Subject(); + + private fetchOrg$ = this.orgId + .pipe( + switchMap((orgId) => this.retrieveCriticalApps(orgId)), + takeUntil(this.teardown), + ) + .subscribe((apps) => this.criticalAppsList.next(apps)); + + constructor( + private keyService: KeyService, + private encryptService: EncryptService, + private criticalAppsApiService: CriticalAppsApiService, + ) {} + + // Get a list of critical apps for a given organization + getAppsListForOrg(orgId: string): Observable { + return this.criticalAppsList + .asObservable() + .pipe(map((apps) => apps.filter((app) => app.organizationId === orgId))); + } + + // Reset the critical apps list + setAppsInListForOrg(apps: PasswordHealthReportApplicationsResponse[]) { + this.criticalAppsList.next(apps); + } + + // Save the selected critical apps for a given organization + async setCriticalApps(orgId: string, selectedUrls: string[]) { + const key = await this.keyService.getOrgKey(orgId); + + // only save records that are not already in the database + const newEntries = await this.filterNewEntries(orgId as OrganizationId, selectedUrls); + const criticalAppsRequests = await this.encryptNewEntries( + orgId as OrganizationId, + key, + newEntries, + ); + + const dbResponse = await firstValueFrom( + this.criticalAppsApiService.saveCriticalApps(criticalAppsRequests), + ); + + // add the new entries to the criticalAppsList + const updatedList = [...this.criticalAppsList.value]; + for (const responseItem of dbResponse) { + const decryptedUrl = await this.encryptService.decryptToUtf8( + new EncString(responseItem.uri), + key, + ); + if (!updatedList.some((f) => f.uri === decryptedUrl)) { + updatedList.push({ + id: responseItem.id, + organizationId: responseItem.organizationId, + uri: decryptedUrl, + } as PasswordHealthReportApplicationsResponse); + } + } + this.criticalAppsList.next(updatedList); + } + + // Get the critical apps for a given organization + setOrganizationId(orgId: OrganizationId) { + this.orgId.next(orgId); + } + + private retrieveCriticalApps( + orgId: OrganizationId | null, + ): Observable { + if (orgId === null) { + return of([]); + } + + const result$ = zip( + this.criticalAppsApiService.getCriticalApps(orgId), + from(this.keyService.getOrgKey(orgId)), + ).pipe( + switchMap(([response, key]) => { + const results = response.map(async (r: PasswordHealthReportApplicationsResponse) => { + const encrypted = new EncString(r.uri); + const uri = await this.encryptService.decryptToUtf8(encrypted, key); + return { id: r.id, organizationId: r.organizationId, uri: uri }; + }); + return forkJoin(results); + }), + first(), + ); + + return result$ as Observable; + } + + private async filterNewEntries(orgId: OrganizationId, selectedUrls: string[]): Promise { + return await firstValueFrom(this.criticalAppsList).then((criticalApps) => { + const criticalAppsUri = criticalApps + .filter((f) => f.organizationId === orgId) + .map((f) => f.uri); + return selectedUrls.filter((url) => !criticalAppsUri.includes(url)); + }); + } + + private async encryptNewEntries( + orgId: OrganizationId, + key: OrgKey, + newEntries: string[], + ): Promise { + const criticalAppsPromises = newEntries.map(async (url) => { + const encryptedUrlName = await this.encryptService.encrypt(url, key); + return { + organizationId: orgId, + url: encryptedUrlName?.encryptedString?.toString() ?? "", + } as PasswordHealthReportApplicationsRequest; + }); + + return await Promise.all(criticalAppsPromises); + } +} + +export interface PasswordHealthReportApplicationsRequest { + organizationId: OrganizationId; + url: string; +} + +export interface PasswordHealthReportApplicationsResponse { + id: PasswordHealthReportApplicationId; + organizationId: OrganizationId; + uri: string; +} + +export type PasswordHealthReportApplicationId = Opaque; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts index a8e62437b9d..f547df31f41 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts @@ -1,4 +1,6 @@ export * from "./member-cipher-details-api.service"; export * from "./password-health.service"; +export * from "./critical-apps.service"; +export * from "./critical-apps-api.service"; export * from "./risk-insights-report.service"; export * from "./risk-insights-data.service"; diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts index 2db7af4bb46..5f461ff6c49 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts @@ -1,14 +1,19 @@ import { NgModule } from "@angular/core"; +import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; +import { CriticalAppsService } from "@bitwarden/bit-common/tools/reports/risk-insights"; import { + CriticalAppsApiService, MemberCipherDetailsApiService, RiskInsightsDataService, RiskInsightsReportService, } from "@bitwarden/bit-common/tools/reports/risk-insights/services"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength/password-strength.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { KeyService } from "@bitwarden/key-management"; import { AccessIntelligenceRoutingModule } from "./access-intelligence-routing.module"; import { RiskInsightsComponent } from "./risk-insights.component"; @@ -33,6 +38,16 @@ import { RiskInsightsComponent } from "./risk-insights.component"; provide: RiskInsightsDataService, deps: [RiskInsightsReportService], }, + safeProvider({ + provide: CriticalAppsService, + useClass: CriticalAppsService, + deps: [KeyService, EncryptService, CriticalAppsApiService], + }), + safeProvider({ + provide: CriticalAppsApiService, + useClass: CriticalAppsApiService, + deps: [ApiService], + }), ], }) export class AccessIntelligenceModule {} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html index e17ac078687..bcc15fbc8fc 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html @@ -55,7 +55,7 @@ buttonType="secondary" bitButton *ngIf="isCriticalAppsFeatureEnabled" - [disabled]="!selectedIds.size" + [disabled]="!selectedUrls.size" [loading]="markingAsCritical" (click)="markAppsAsCritical()" > @@ -80,9 +80,11 @@ + {{ r.applicationName }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index 5fb12fed090..b22b94599f9 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -1,15 +1,17 @@ -import { Component, DestroyRef, OnDestroy, OnInit, inject } from "@angular/core"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { debounceTime, map, Observable, of, Subscription } from "rxjs"; +import { combineLatest, debounceTime, map, Observable, of, skipWhile } from "rxjs"; import { + CriticalAppsService, RiskInsightsDataService, RiskInsightsReportService, } from "@bitwarden/bit-common/tools/reports/risk-insights"; import { ApplicationHealthReportDetail, + ApplicationHealthReportDetailWithCriticalFlag, ApplicationHealthReportSummary, } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -50,16 +52,15 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" SharedModule, ], }) -export class AllApplicationsComponent implements OnInit, OnDestroy { - protected dataSource = new TableDataSource(); - protected selectedIds: Set = new Set(); +export class AllApplicationsComponent implements OnInit { + protected dataSource = new TableDataSource(); + protected selectedUrls: Set = new Set(); protected searchControl = new FormControl("", { nonNullable: true }); protected loading = true; protected organization = {} as Organization; noItemsIcon = Icons.Security; protected markingAsCritical = false; protected applicationSummary = {} as ApplicationHealthReportSummary; - private subscription = new Subscription(); destroyRef = inject(DestroyRef); isLoading$: Observable = of(false); @@ -70,28 +71,33 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { FeatureFlag.CriticalApps, ); - const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); + const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; + combineLatest([ + this.dataService.applications$, + this.criticalAppsService.getAppsListForOrg(organizationId), + this.organizationService.get$(organizationId), + ]) + .pipe( + takeUntilDestroyed(this.destroyRef), + skipWhile(([_, __, organization]) => !organization), + map(([applications, criticalApps, organization]) => { + const criticalUrls = criticalApps.map((ca) => ca.uri); + const data = applications?.map((app) => ({ + ...app, + isMarkedAsCritical: criticalUrls.includes(app.applicationName), + })) as ApplicationHealthReportDetailWithCriticalFlag[]; + return { data, organization }; + }), + ) + .subscribe(({ data, organization }) => { + this.dataSource.data = data ?? []; + this.applicationSummary = this.reportService.generateApplicationsSummary(data ?? []); + if (organization) { + this.organization = organization; + } + }); - if (organizationId) { - this.organization = await this.organizationService.get(organizationId); - this.subscription = this.dataService.applications$ - .pipe( - map((applications) => { - if (applications) { - this.dataSource.data = applications; - this.applicationSummary = - this.reportService.generateApplicationsSummary(applications); - } - }), - takeUntilDestroyed(this.destroyRef), - ) - .subscribe(); - this.isLoading$ = this.dataService.isLoading$; - } - } - - ngOnDestroy(): void { - this.subscription?.unsubscribe(); + this.isLoading$ = this.dataService.isLoading$; } constructor( @@ -103,6 +109,7 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { protected dataService: RiskInsightsDataService, protected organizationService: OrganizationService, protected reportService: RiskInsightsReportService, + protected criticalAppsService: CriticalAppsService, protected dialogService: DialogService, ) { this.searchControl.valueChanges @@ -119,21 +126,28 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { }); }; + isMarkedAsCriticalItem(applicationName: string) { + return this.selectedUrls.has(applicationName); + } + markAppsAsCritical = async () => { - // TODO: Send to API once implemented this.markingAsCritical = true; - return new Promise((resolve) => { - setTimeout(() => { - this.selectedIds.clear(); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("appsMarkedAsCritical"), - }); - resolve(true); - this.markingAsCritical = false; - }, 1000); - }); + + try { + await this.criticalAppsService.setCriticalApps( + this.organization.id, + Array.from(this.selectedUrls), + ); + + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("appsMarkedAsCritical"), + }); + } finally { + this.selectedUrls.clear(); + this.markingAsCritical = false; + } }; trackByFunction(_: number, item: ApplicationHealthReportDetail) { @@ -161,12 +175,14 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { }); }; - onCheckboxChange(id: number, event: Event) { + onCheckboxChange(applicationName: string, event: Event) { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { - this.selectedIds.add(id); + this.selectedUrls.add(applicationName); } else { - this.selectedIds.delete(id); + this.selectedUrls.delete(applicationName); } } + + getSelectedUrls = () => Array.from(this.selectedUrls); } diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts index 4df363ab2c7..4dffa60b562 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts @@ -6,6 +6,7 @@ export const applicationTableMockData = [ totalPasswords: 10, atRiskMembers: 2, totalMembers: 5, + isMarkedAsCritical: false, }, { id: 2, @@ -14,6 +15,7 @@ export const applicationTableMockData = [ totalPasswords: 8, atRiskMembers: 1, totalMembers: 3, + isMarkedAsCritical: false, }, { id: 3, @@ -22,6 +24,7 @@ export const applicationTableMockData = [ totalPasswords: 6, atRiskMembers: 0, totalMembers: 2, + isMarkedAsCritical: false, }, { id: 4, @@ -30,6 +33,7 @@ export const applicationTableMockData = [ totalPasswords: 4, atRiskMembers: 0, totalMembers: 1, + isMarkedAsCritical: false, }, { id: 5, @@ -38,6 +42,7 @@ export const applicationTableMockData = [ totalPasswords: 2, atRiskMembers: 0, totalMembers: 0, + isMarkedAsCritical: false, }, { id: 6, @@ -46,5 +51,6 @@ export const applicationTableMockData = [ totalPasswords: 1, atRiskMembers: 0, totalMembers: 0, + isMarkedAsCritical: false, }, ]; diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html index 7fe320ede6a..ae8bd94e5f3 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html @@ -40,7 +40,7 @@ - {{ "criticalApplicationsWithCount" | i18n: criticalAppsCount }} + {{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts index 75601994c70..5adb0d32945 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts @@ -6,11 +6,17 @@ import { Observable, EMPTY } from "rxjs"; import { map, switchMap } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { RiskInsightsDataService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + RiskInsightsDataService, + CriticalAppsService, + PasswordHealthReportApplicationsResponse, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; import { ApplicationHealthReportDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +// eslint-disable-next-line no-restricted-imports -- used for dependency injection import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; @@ -51,6 +57,7 @@ export class RiskInsightsComponent implements OnInit { dataLastUpdated: Date = new Date(); isCriticalAppsFeatureEnabled: boolean = false; + criticalApps$: Observable = new Observable(); showDebugTabs: boolean = false; appsCount: number = 0; @@ -69,10 +76,13 @@ export class RiskInsightsComponent implements OnInit { private router: Router, private configService: ConfigService, private dataService: RiskInsightsDataService, + private criticalAppsService: CriticalAppsService, ) { this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps; }); + const orgId = this.route.snapshot.paramMap.get("organizationId") ?? ""; + this.criticalApps$ = this.criticalAppsService.getAppsListForOrg(orgId); } async ngOnInit() { @@ -104,6 +114,7 @@ export class RiskInsightsComponent implements OnInit { if (applications) { this.appsCount = applications.length; } + this.criticalAppsService.setOrganizationId(this.organizationId as OrganizationId); }, }); } From 8942f8d440f1289cf2c9a1dff94237901d9487d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:56:48 +0000 Subject: [PATCH 215/270] [deps] SM: Update lint-staged to v15.4.0 (#10565) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 38 +++++++++++++++++++------------------- package.json | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index b94ad3b762f..4b30fedbd06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,7 +156,7 @@ "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", - "lint-staged": "15.2.8", + "lint-staged": "15.4.0", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.49", @@ -14661,9 +14661,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -22338,22 +22338,22 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "15.2.8", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.8.tgz", - "integrity": "sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.0.tgz", + "integrity": "sha512-UdODqEZiQimd7rCzZ2vqFuELRNUda3mdv7M93jhE4SmDiqAj/w/msvwKgagH23jv2iCPw6Q5m+ltX4VlHvp2LQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "~5.3.0", + "chalk": "~5.4.1", "commander": "~12.1.0", - "debug": "~4.3.6", + "debug": "~4.4.0", "execa": "~8.0.1", - "lilconfig": "~3.1.2", - "listr2": "~8.2.4", - "micromatch": "~4.0.7", + "lilconfig": "~3.1.3", + "listr2": "~8.2.5", + "micromatch": "~4.0.8", "pidtree": "~0.6.0", "string-argv": "~0.3.2", - "yaml": "~2.5.0" + "yaml": "~2.6.1" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -22366,9 +22366,9 @@ } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, "license": "MIT", "engines": { @@ -33151,9 +33151,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 07e3f217867..5d7c2ace64d 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", - "lint-staged": "15.2.8", + "lint-staged": "15.4.0", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.49", From cc311d9a9258e66b2af9eac8749383d8427e2da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Thu, 16 Jan 2025 10:02:28 -0500 Subject: [PATCH 216/270] [PM-16788] introduce generator metadata (#12757) --- .../core/src/metadata/algorithm-metadata.ts | 61 +++++ .../tools/generator/core/src/metadata/data.ts | 48 ++++ .../core/src/metadata/email/catchall.spec.ts | 65 ++++++ .../core/src/metadata/email/catchall.ts | 70 ++++++ .../core/src/metadata/email/forwarder.ts | 4 + .../src/metadata/email/plus-address.spec.ts | 65 ++++++ .../core/src/metadata/email/plus-address.ts | 72 ++++++ .../core/src/metadata/generator-metadata.ts | 29 +++ .../metadata/password/eff-word-list.spec.ts | 102 ++++++++ .../src/metadata/password/eff-word-list.ts | 91 ++++++++ .../metadata/password/random-password.spec.ts | 105 +++++++++ .../src/metadata/password/random-password.ts | 117 ++++++++++ .../core/src/metadata/profile-metadata.ts | 80 +++++++ .../tools/generator/core/src/metadata/type.ts | 28 +++ .../metadata/username/eff-word-list.spec.ts | 58 +++++ .../src/metadata/username/eff-word-list.ts | 70 ++++++ .../generator/core/src/metadata/util.spec.ts | 218 ++++++++++++++++++ .../tools/generator/core/src/metadata/util.ts | 60 +++++ .../core/src/policies/catchall-constraints.ts | 2 +- .../src/types/password-generation-options.ts | 20 +- libs/tools/generator/core/src/util.ts | 2 +- 21 files changed, 1355 insertions(+), 12 deletions(-) create mode 100644 libs/tools/generator/core/src/metadata/algorithm-metadata.ts create mode 100644 libs/tools/generator/core/src/metadata/data.ts create mode 100644 libs/tools/generator/core/src/metadata/email/catchall.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/email/catchall.ts create mode 100644 libs/tools/generator/core/src/metadata/email/forwarder.ts create mode 100644 libs/tools/generator/core/src/metadata/email/plus-address.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/email/plus-address.ts create mode 100644 libs/tools/generator/core/src/metadata/generator-metadata.ts create mode 100644 libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/password/eff-word-list.ts create mode 100644 libs/tools/generator/core/src/metadata/password/random-password.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/password/random-password.ts create mode 100644 libs/tools/generator/core/src/metadata/profile-metadata.ts create mode 100644 libs/tools/generator/core/src/metadata/type.ts create mode 100644 libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/username/eff-word-list.ts create mode 100644 libs/tools/generator/core/src/metadata/util.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/util.ts diff --git a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts new file mode 100644 index 00000000000..f776dd76e54 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts @@ -0,0 +1,61 @@ +import { CredentialAlgorithm, CredentialType } from "./type"; + +/** Credential generator metadata common across credential generators */ +export type AlgorithmMetadata = { + /** Uniquely identifies the credential configuration + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : AlgorithmMetadata = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ + id: CredentialAlgorithm; + + /** The kind of credential generated by this configuration */ + category: CredentialType; + + /** Used to order credential algorithms for display purposes. + * Items with lesser weights appear before entries with greater + * weights (i.e. ascending sort). + */ + weight: number; + + /** Localization keys */ + i18nKeys: { + /** descriptive name of the algorithm */ + name: string; + + /** explanatory text for the algorithm */ + description?: string; + + /** labels the generate action */ + generateCredential: string; + + /** message informing users when the generator produces a new credential */ + credentialGenerated: string; + + /* labels the action that assigns a generated value to a domain object */ + useCredential: string; + + /** labels the generated output */ + credentialType: string; + + /** labels the copy output action */ + copyCredential: string; + }; + + /** fine-tunings for generator user experiences */ + capabilities: { + /** `true` when the generator supports autogeneration + * @remarks this property is useful when credential generation + * carries side effects, such as configuring a service external + * to Bitwarden. + */ + autogenerate: boolean; + + /** Well-known fields to display on the options panel or collect from the environment. + * @remarks: at present, this is only used by forwarders + */ + fields: string[]; + }; +}; diff --git a/libs/tools/generator/core/src/metadata/data.ts b/libs/tools/generator/core/src/metadata/data.ts new file mode 100644 index 00000000000..2b9dad50557 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/data.ts @@ -0,0 +1,48 @@ +import { deepFreeze } from "@bitwarden/common/tools/util"; + +/** algorithms for generating credentials */ +export const Algorithm = Object.freeze({ + /** A password composed of random characters */ + password: "password", + + /** A password composed of random words from the EFF word list */ + passphrase: "passphrase", + + /** A username composed of words from the EFF word list */ + username: "username", + + /** An email username composed of random characters */ + catchall: "catchall", + + /** An email username composed of words from the EFF word list */ + plusAddress: "subaddress", +} as const); + +/** categorizes credentials according to their use-case outside of Bitwarden */ +export const Type = Object.freeze({ + password: "password", + username: "username", + email: "email", +} as const); + +/** categorizes settings according to their expected use-case within Bitwarden */ +export const Profile = Object.freeze({ + /** account-level generator options. This is the default. + * @remarks these are the options displayed on the generator tab + */ + account: "account", + + // FIXME: consider adding a profile for bitwarden's master password +}); + +/** Credential generation algorithms grouped by purpose. */ +export const AlgorithmsByType = deepFreeze({ + /** Algorithms that produce passwords */ + [Type.password]: [Algorithm.password, Algorithm.passphrase] as const, + + /** Algorithms that produce usernames */ + [Type.username]: [Algorithm.username] as const, + + /** Algorithms that produce email addresses */ + [Type.email]: [Algorithm.catchall, Algorithm.plusAddress] as const, +} as const); diff --git a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts new file mode 100644 index 00000000000..f63f141842c --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts @@ -0,0 +1,65 @@ +import { mock } from "jest-mock-extended"; + +import { EmailRandomizer } from "../../engine"; +import { CatchallConstraints } from "../../policies/catchall-constraints"; +import { CatchallGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import catchall from "./catchall"; + +const dependencyProvider = mock(); + +describe("email - catchall generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(catchall.engine.create(dependencyProvider)).toBeInstanceOf(EmailRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = catchall.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: CatchallGenerationOptions = { + catchallType: "random", + catchallDomain: "example.com", + }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a catchall constraints", () => { + const context = { defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(CatchallConstraints); + }); + + it("extracts the domain from context.email", () => { + const context = { email: "foo@example.com", defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context) as CatchallConstraints; + + expect(constraints.domain).toEqual("example.com"); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/email/catchall.ts b/libs/tools/generator/core/src/metadata/email/catchall.ts new file mode 100644 index 00000000000..0711e5c3719 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/catchall.ts @@ -0,0 +1,70 @@ +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { EmailRandomizer } from "../../engine"; +import { CatchallConstraints } from "../../policies/catchall-constraints"; +import { + CatchallGenerationOptions, + CredentialGenerator, + GeneratorDependencyProvider, +} from "../../types"; +import { Algorithm, Type, Profile } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const catchall: GeneratorMetadata = deepFreeze({ + id: Algorithm.catchall, + category: Type.email, + weight: 210, + i18nKeys: { + name: "catchallEmail", + description: "catchallEmailDesc", + credentialType: "email", + generateCredential: "generateEmail", + credentialGenerated: "emailGenerated", + copyCredential: "copyEmail", + useCredential: "useThisEmail", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new EmailRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "catchallGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "catchallType", + "catchallDomain", + ]), + state: GENERATOR_DISK, + initial: { + catchallType: "random", + catchallDomain: "", + }, + options: { + deserializer: (value) => value, + clearOn: ["logout"], + }, + }, + constraints: { + default: { catchallDomain: { minLength: 1 } }, + create(_policies, context) { + return new CatchallConstraints(context.email ?? ""); + }, + }, + }, + }, +}); + +export default catchall; diff --git a/libs/tools/generator/core/src/metadata/email/forwarder.ts b/libs/tools/generator/core/src/metadata/email/forwarder.ts new file mode 100644 index 00000000000..1dfc219d466 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/forwarder.ts @@ -0,0 +1,4 @@ +// Forwarders are pending integration with the extension API +// +// They use the 300-block of weights and derive their metadata +// using logic similar to `toCredentialGeneratorConfiguration` 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 new file mode 100644 index 00000000000..2ac7645ed30 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts @@ -0,0 +1,65 @@ +import { mock } from "jest-mock-extended"; + +import { EmailRandomizer } from "../../engine"; +import { SubaddressConstraints } from "../../policies/subaddress-constraints"; +import { SubaddressGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import plusAddress from "./plus-address"; + +const dependencyProvider = mock(); + +describe("email - plus address generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(plusAddress.engine.create(dependencyProvider)).toBeInstanceOf(EmailRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = plusAddress.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: SubaddressGenerationOptions = { + subaddressType: "random", + subaddressEmail: "foo@example.com", + }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a subaddress constraints", () => { + const context = { defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(SubaddressConstraints); + }); + + it("sets the constraint email to context.email", () => { + const context = { email: "bar@example.com", defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context) as SubaddressConstraints; + + expect(constraints.email).toEqual("bar@example.com"); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.ts b/libs/tools/generator/core/src/metadata/email/plus-address.ts new file mode 100644 index 00000000000..0db0acd415c --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/plus-address.ts @@ -0,0 +1,72 @@ +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { EmailRandomizer } from "../../engine"; +import { SubaddressConstraints } from "../../policies/subaddress-constraints"; +import { + CredentialGenerator, + GeneratorDependencyProvider, + SubaddressGenerationOptions, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const plusAddress: GeneratorMetadata = deepFreeze({ + id: Algorithm.plusAddress, + category: Type.email, + weight: 200, + i18nKeys: { + name: "plusAddressedEmail", + description: "plusAddressedEmailDesc", + credentialType: "email", + generateCredential: "generateEmail", + credentialGenerated: "emailGenerated", + copyCredential: "copyEmail", + useCredential: "useThisEmail", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new EmailRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "subaddressGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "subaddressType", + "subaddressEmail", + ]), + state: GENERATOR_DISK, + initial: { + subaddressType: "random", + subaddressEmail: "", + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + }, + constraints: { + default: {}, + create(_policy, context) { + return new SubaddressConstraints(context.email ?? ""); + }, + }, + }, + }, +}); + +export default plusAddress; diff --git a/libs/tools/generator/core/src/metadata/generator-metadata.ts b/libs/tools/generator/core/src/metadata/generator-metadata.ts new file mode 100644 index 00000000000..9296d30430e --- /dev/null +++ b/libs/tools/generator/core/src/metadata/generator-metadata.ts @@ -0,0 +1,29 @@ +import { CredentialGenerator, GeneratorDependencyProvider } from "../types"; + +import { AlgorithmMetadata } from "./algorithm-metadata"; +import { Profile } from "./data"; +import { ProfileMetadata } from "./profile-metadata"; + +/** Extends the algorithm metadata with storage and engine configurations. + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : CredentialGeneratorInfo = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ +export type GeneratorMetadata = AlgorithmMetadata & { + /** An algorithm that generates credentials when ran. */ + engine: { + /** Factory for the generator + */ + create: (randomizer: GeneratorDependencyProvider) => CredentialGenerator; + }; + + /** Defines parameters for credential generation */ + profiles: { + /** profiles supported by this generator; when `undefined`, + * the generator does not support the profile. + */ + [K in keyof typeof Profile]?: ProfileMetadata; + }; +}; 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 new file mode 100644 index 00000000000..57961a60033 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts @@ -0,0 +1,102 @@ +import { mock } from "jest-mock-extended"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; + +import { PasswordRandomizer } from "../../engine"; +import { PassphrasePolicyConstraints } from "../../policies"; +import { PassphraseGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import effPassphrase from "./eff-word-list"; + +const dependencyProvider = mock(); + +describe("password - eff words generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(effPassphrase.engine.create(dependencyProvider)).toBeInstanceOf(PasswordRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = effPassphrase.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: PassphraseGenerationOptions = { ...accountProfile.storage.initial }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a passphrase policy constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(PassphrasePolicyConstraints); + }); + + it("forwards the policy to the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + data: { + minNumberWords: 6, + capitalize: false, + includeNumber: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.numWords.min).toEqual(6); + }); + + it("combines multiple policies in the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + data: { + minNumberWords: 6, + capitalize: false, + includeNumber: false, + }, + }, + { + type: PolicyType.PasswordGenerator, + data: { + minNumberWords: 3, + capitalize: true, + includeNumber: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.numWords.min).toEqual(6); + expect(constraints.constraints.capitalize.requiredValue).toEqual(true); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts new file mode 100644 index 00000000000..fc86032bf6b --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts @@ -0,0 +1,91 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; + +import { PasswordRandomizer } from "../../engine"; +import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; +import { + CredentialGenerator, + GeneratorDependencyProvider, + PassphraseGenerationOptions, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const passphrase: GeneratorMetadata = { + id: Algorithm.passphrase, + category: Type.password, + weight: 110, + i18nKeys: { + name: "passphrase", + credentialType: "passphrase", + generateCredential: "generatePassphrase", + credentialGenerated: "passphraseGenerated", + copyCredential: "copyPassphrase", + useCredential: "useThisPassphrase", + }, + capabilities: { + autogenerate: false, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new PasswordRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "passphraseGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "numWords", + "wordSeparator", + "capitalize", + "includeNumber", + ]), + state: GENERATOR_DISK, + initial: { + numWords: 6, + wordSeparator: "-", + capitalize: false, + includeNumber: false, + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + } satisfies ObjectKey, + constraints: { + type: PolicyType.PasswordGenerator, + default: { + wordSeparator: { maxLength: 1 }, + numWords: { + min: 3, + max: 20, + recommendation: 6, + }, + }, + create(policies, context) { + const initial = { + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }; + const policy = policies.reduce(passphraseLeastPrivilege, initial); + const constraints = new PassphrasePolicyConstraints(policy, context.defaultConstraints); + return constraints; + }, + }, + }, + }, +}; + +export default passphrase; 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 new file mode 100644 index 00000000000..d91ceaac248 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts @@ -0,0 +1,105 @@ +import { mock } from "jest-mock-extended"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; + +import { PasswordRandomizer } from "../../engine"; +import { DynamicPasswordPolicyConstraints } from "../../policies"; +import { PasswordGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import password from "./random-password"; + +const dependencyProvider = mock(); + +describe("password - characters generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(password.engine.create(dependencyProvider)).toBeInstanceOf(PasswordRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = password.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: PasswordGenerationOptions = { ...accountProfile.storage.initial }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a passphrase policy constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(DynamicPasswordPolicyConstraints); + }); + + it("forwards the policy to the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + enabled: true, + data: { + minLength: 10, + capitalize: false, + useNumbers: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.length.min).toEqual(10); + }); + + it("combines multiple policies in the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + enabled: true, + data: { + minLength: 14, + useSpecial: false, + useNumbers: false, + }, + }, + { + type: PolicyType.PasswordGenerator, + enabled: true, + data: { + minLength: 10, + useSpecial: true, + includeNumber: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.length.min).toEqual(14); + expect(constraints.constraints.special.requiredValue).toEqual(true); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/password/random-password.ts b/libs/tools/generator/core/src/metadata/password/random-password.ts new file mode 100644 index 00000000000..693236b0967 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/random-password.ts @@ -0,0 +1,117 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { PasswordRandomizer } from "../../engine"; +import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; +import { + CredentialGenerator, + GeneratorDependencyProvider, + PasswordGeneratorSettings, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const password: GeneratorMetadata = deepFreeze({ + id: Algorithm.password, + category: Type.password, + weight: 100, + i18nKeys: { + name: "password", + generateCredential: "generatePassword", + credentialGenerated: "passwordGenerated", + credentialType: "password", + copyCredential: "copyPassword", + useCredential: "useThisPassword", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new PasswordRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "passwordGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "length", + "ambiguous", + "uppercase", + "minUppercase", + "lowercase", + "minLowercase", + "number", + "minNumber", + "special", + "minSpecial", + ]), + state: GENERATOR_DISK, + initial: { + length: 14, + ambiguous: true, + uppercase: true, + minUppercase: 1, + lowercase: true, + minLowercase: 1, + number: true, + minNumber: 1, + special: false, + minSpecial: 0, + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + }, + constraints: { + type: PolicyType.PasswordGenerator, + default: { + length: { + min: 5, + max: 128, + recommendation: 14, + }, + minNumber: { + min: 0, + max: 9, + }, + minSpecial: { + min: 0, + max: 9, + }, + }, + create(policies, context) { + const initial = { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }; + const policy = policies.reduce(passwordLeastPrivilege, initial); + const constraints = new DynamicPasswordPolicyConstraints( + policy, + context.defaultConstraints, + ); + return constraints; + }, + }, + }, + }, +}); + +export default password; diff --git a/libs/tools/generator/core/src/metadata/profile-metadata.ts b/libs/tools/generator/core/src/metadata/profile-metadata.ts new file mode 100644 index 00000000000..4ac9139f632 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/profile-metadata.ts @@ -0,0 +1,80 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { SiteId } from "@bitwarden/common/tools/extension"; +import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; +import { Constraints } from "@bitwarden/common/tools/types"; + +import { GeneratorConstraints } from "../types"; + +export type ProfileContext = { + /** The email address for the current user; + * `undefined` when no email is available. + */ + email?: string; + + /** Default application limits for the profile */ + defaultConstraints: Constraints; +}; + +type ProfileConstraints = { + /** The key used to locate this profile's policies in the admin console. + * When this type is undefined, no policy is defined for the profile. + */ + type?: PolicyType; + + /** default application limits for this profile; these are overridden + * by the policy + */ + default: Constraints; + + /** Constructs generator constraints from a policy. + * @param policies the administrative policy to apply to the provided constraints + * When `type` is undefined then `policy` is `undefined` this is an empty array. + * @param defaultConstraints application constraints; typically those defined in + * the `default` member, above. + * @returns the generator constraints to apply to this profile's options. + */ + create: (policies: Policy[], context: ProfileContext) => GeneratorConstraints; +}; + +/** Generator profiles partition generator operations + * according to where they're used within the password + * manager. Core profiles store their data using the + * generator's system storage. + */ +export type CoreProfileMetadata = { + /** distinguishes profile metadata types */ + type: "core"; + + /** plaintext import buffer */ + import?: ObjectKey, Options> & { format: "plain" }; + + /** persistent storage location */ + storage: ObjectKey; + + /** policy enforced when saving the options */ + constraints: ProfileConstraints; +}; + +/** Generator profiles partition generator operations + * according to where they're used within the password + * manager. Extension profiles store their data + * using the extension system. + */ +export type ExtensionProfileMetadata = { + /** distinguishes profile metadata types */ + type: "extension"; + + /** The extension site described by this metadata */ + site: Site; + + constraints: ProfileConstraints; +}; + +/** Generator profiles partition generator operations + * according to where they're used within the password + * manager + */ +export type ProfileMetadata = + | CoreProfileMetadata + | ExtensionProfileMetadata; diff --git a/libs/tools/generator/core/src/metadata/type.ts b/libs/tools/generator/core/src/metadata/type.ts new file mode 100644 index 00000000000..924b92883e5 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/type.ts @@ -0,0 +1,28 @@ +import { VendorId } from "@bitwarden/common/tools/extension"; + +import { AlgorithmsByType, Profile, Type } from "./data"; + +/** categorizes credentials according to their use-case outside of Bitwarden */ +export type CredentialType = keyof typeof Type; + +/** categorizes credentials according to their expected use-case within Bitwarden */ +export type GeneratorProfile = keyof typeof Profile; + +/** A type of password that may be generated by the credential generator. */ +export type PasswordAlgorithm = (typeof AlgorithmsByType.password)[number]; + +/** A type of username that may be generated by the credential generator. */ +export type UsernameAlgorithm = (typeof AlgorithmsByType.username)[number]; + +/** A type of email address that may be generated by the credential generator. */ +export type EmailAlgorithm = (typeof AlgorithmsByType.email)[number] | ForwarderExtensionId; + +/** Identifies a forwarding service */ +export type ForwarderExtensionId = { forwarder: VendorId }; + +/** A type of credential that can be generated by the credential generator. */ +// this is defined in terms of `AlgorithmsByType` to typecheck the keys of +// `AlgorithmsByType` against the keys of `CredentialType`. +export type CredentialAlgorithm = + | (typeof AlgorithmsByType)[CredentialType][number] + | ForwarderExtensionId; 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 new file mode 100644 index 00000000000..aba9680a448 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts @@ -0,0 +1,58 @@ +import { mock } from "jest-mock-extended"; + +import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; + +import { UsernameRandomizer } from "../../engine"; +import { EffUsernameGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import effWordList from "./eff-word-list"; + +const dependencyProvider = mock(); + +describe("username - eff words generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(effWordList.engine.create(dependencyProvider)).toBeInstanceOf(UsernameRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = effWordList.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: EffUsernameGenerationOptions = { + wordCapitalize: true, + wordIncludeNumber: true, + }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a effWordList constraints", () => { + const context = { defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(IdentityConstraint); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts new file mode 100644 index 00000000000..6373daf8ed5 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts @@ -0,0 +1,70 @@ +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { UsernameRandomizer } from "../../engine"; +import { + CredentialGenerator, + EffUsernameGenerationOptions, + GeneratorDependencyProvider, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const effWordList: GeneratorMetadata = deepFreeze({ + id: Algorithm.username, + category: Type.username, + weight: 400, + i18nKeys: { + name: "randomWord", + credentialType: "username", + generateCredential: "generateUsername", + credentialGenerated: "usernameGenerated", + copyCredential: "copyUsername", + useCredential: "useThisUsername", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new UsernameRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "effUsernameGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "wordCapitalize", + "wordIncludeNumber", + ]), + state: GENERATOR_DISK, + initial: { + wordCapitalize: false, + wordIncludeNumber: false, + website: null, + }, + options: { + deserializer: (value) => value, + clearOn: ["logout"], + }, + }, + constraints: { + default: {}, + create(_policies, _context) { + return new IdentityConstraint(); + }, + }, + }, + }, +}); + +export default effWordList; diff --git a/libs/tools/generator/core/src/metadata/util.spec.ts b/libs/tools/generator/core/src/metadata/util.spec.ts new file mode 100644 index 00000000000..2283699140b --- /dev/null +++ b/libs/tools/generator/core/src/metadata/util.spec.ts @@ -0,0 +1,218 @@ +import { VendorId } from "@bitwarden/common/tools/extension"; + +import { Algorithm, AlgorithmsByType } from "./data"; +import { ProfileMetadata } from "./profile-metadata"; +import { + isPasswordAlgorithm, + isUsernameAlgorithm, + isForwarderExtensionId, + isEmailAlgorithm, + isSameAlgorithm, + isCoreProfile, + isForwarderProfile, +} from "./util"; + +describe("credential generator metadata utility functions", () => { + describe("isPasswordAlgorithm", () => { + it("returns `true` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isPasswordAlgorithm(algorithm)).toBe(true); + } + }); + + it("returns `false` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isPasswordAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isPasswordAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is a forwarder extension", () => { + expect(isPasswordAlgorithm({ forwarder: "bitwarden" as VendorId })).toBe(false); + }); + }); + + describe("isUsernameAlgorithm", () => { + it("returns `false` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isUsernameAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isUsernameAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isUsernameAlgorithm(algorithm)).toBe(true); + } + }); + + it("returns `false` when the algorithm is a forwarder extension", () => { + expect(isUsernameAlgorithm({ forwarder: "bitwarden" as VendorId })).toBe(false); + }); + }); + + describe("isForwarderExtensionId", () => { + it("returns `false` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isForwarderExtensionId(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isForwarderExtensionId(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isForwarderExtensionId(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is a forwarder extension", () => { + expect(isForwarderExtensionId({ forwarder: "bitwarden" as VendorId })).toBe(true); + }); + }); + + describe("isEmailAlgorithm", () => { + it("returns `false` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isEmailAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isEmailAlgorithm(algorithm)).toBe(true); + } + }); + + it("returns `false` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isEmailAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is a forwarder extension", () => { + expect(isEmailAlgorithm({ forwarder: "bitwarden" as VendorId })).toBe(true); + }); + }); + + describe("isSameAlgorithm", () => { + it("returns `true` when the algorithms are equal", () => { + // identical object + expect(isSameAlgorithm(Algorithm.catchall, Algorithm.catchall)).toBe(true); + + // equal object + expect(isSameAlgorithm(Algorithm.catchall, `${Algorithm.catchall}`)).toBe(true); + }); + + it("returns `false` when the algorithms are different", () => { + // not an exhaustive list + expect(isSameAlgorithm(Algorithm.catchall, Algorithm.passphrase)).toBe(false); + expect(isSameAlgorithm(Algorithm.passphrase, Algorithm.password)).toBe(false); + expect(isSameAlgorithm(Algorithm.password, Algorithm.plusAddress)).toBe(false); + expect(isSameAlgorithm(Algorithm.plusAddress, Algorithm.username)).toBe(false); + expect(isSameAlgorithm(Algorithm.username, Algorithm.passphrase)).toBe(false); + }); + + it("returns `true` when the algorithms refer to a forwarder with a matching vendor", () => { + const someVendor = { forwarder: "bitwarden" as VendorId }; + const sameVendor = { forwarder: "bitwarden" as VendorId }; + expect(isSameAlgorithm(someVendor, sameVendor)).toBe(true); + }); + + it("returns `false` when the algorithms refer to a forwarder with a different vendor", () => { + const someVendor = { forwarder: "bitwarden" as VendorId }; + const sameVendor = { forwarder: "bytewarden" as VendorId }; + expect(isSameAlgorithm(someVendor, sameVendor)).toBe(false); + }); + + it("returns `false` when the algorithms refer to a forwarder and a core algorithm", () => { + const someVendor = { forwarder: "bitwarden" as VendorId }; + // not an exhaustive list + expect(isSameAlgorithm(someVendor, Algorithm.plusAddress)).toBe(false); + expect(isSameAlgorithm(Algorithm.username, someVendor)).toBe(false); + }); + }); + + describe("isCoreProfile", () => { + it("returns `true` when the profile's type is `core`", () => { + const profile: ProfileMetadata = { + type: "core", + storage: null, + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isCoreProfile(profile)).toBe(true); + }); + + it("returns `false` when the profile's type is `extension`", () => { + const profile: ProfileMetadata = { + type: "extension", + site: "forwarder", + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isCoreProfile(profile)).toBe(false); + }); + }); + + describe("isForwarderProfile", () => { + it("returns `false` when the profile's type is `core`", () => { + const profile: ProfileMetadata = { + type: "core", + storage: null, + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isForwarderProfile(profile)).toBe(false); + }); + + it("returns `true` when the profile's type is `extension` and the site is `forwarder`", () => { + const profile: ProfileMetadata = { + type: "extension", + site: "forwarder", + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isForwarderProfile(profile)).toBe(true); + }); + + it("returns `false` when the profile's type is `extension` and the site is not `forwarder`", () => { + const profile: ProfileMetadata = { + type: "extension", + site: "not-a-forwarder" as any, + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isForwarderProfile(profile)).toBe(false); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/util.ts b/libs/tools/generator/core/src/metadata/util.ts new file mode 100644 index 00000000000..e85061720ad --- /dev/null +++ b/libs/tools/generator/core/src/metadata/util.ts @@ -0,0 +1,60 @@ +import { AlgorithmsByType } from "./data"; +import { CoreProfileMetadata, ExtensionProfileMetadata, ProfileMetadata } from "./profile-metadata"; +import { + CredentialAlgorithm, + EmailAlgorithm, + ForwarderExtensionId, + PasswordAlgorithm, + UsernameAlgorithm, +} from "./type"; + +/** Returns true when the input algorithm is a password algorithm. */ +export function isPasswordAlgorithm( + algorithm: CredentialAlgorithm, +): algorithm is PasswordAlgorithm { + return AlgorithmsByType.password.includes(algorithm as any); +} + +/** Returns true when the input algorithm is a username algorithm. */ +export function isUsernameAlgorithm( + algorithm: CredentialAlgorithm, +): algorithm is UsernameAlgorithm { + return AlgorithmsByType.username.includes(algorithm as any); +} + +/** Returns true when the input algorithm is a forwarder integration. */ +export function isForwarderExtensionId( + algorithm: CredentialAlgorithm, +): algorithm is ForwarderExtensionId { + return algorithm && typeof algorithm === "object" && "forwarder" in algorithm; +} + +/** Returns true when the input algorithm is an email algorithm. */ +export function isEmailAlgorithm(algorithm: CredentialAlgorithm): algorithm is EmailAlgorithm { + return AlgorithmsByType.email.includes(algorithm as any) || isForwarderExtensionId(algorithm); +} + +/** Returns true when the algorithms are the same. */ +export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorithm) { + if (lhs === rhs) { + return true; + } else if (isForwarderExtensionId(lhs) && isForwarderExtensionId(rhs)) { + return lhs.forwarder === rhs.forwarder; + } else { + return false; + } +} + +/** Returns true when the input describes a core profile. */ +export function isCoreProfile( + value: ProfileMetadata, +): value is CoreProfileMetadata { + return value.type === "core"; +} + +/** Returns true when the input describes a forwarder extension profile. */ +export function isForwarderProfile( + value: ProfileMetadata, +): value is ExtensionProfileMetadata { + return value.type === "extension" && value.site === "forwarder"; +} diff --git a/libs/tools/generator/core/src/policies/catchall-constraints.ts b/libs/tools/generator/core/src/policies/catchall-constraints.ts index 47476a304a9..7793180988d 100644 --- a/libs/tools/generator/core/src/policies/catchall-constraints.ts +++ b/libs/tools/generator/core/src/policies/catchall-constraints.ts @@ -24,7 +24,7 @@ export class CatchallConstraints implements StateConstraints> = {}; diff --git a/libs/tools/generator/core/src/types/password-generation-options.ts b/libs/tools/generator/core/src/types/password-generation-options.ts index 76e8827d4de..7a8a538c409 100644 --- a/libs/tools/generator/core/src/types/password-generation-options.ts +++ b/libs/tools/generator/core/src/types/password-generation-options.ts @@ -2,58 +2,58 @@ */ export type PasswordGeneratorSettings = { /** The length of the password selected by the user */ - length: number; + length?: number; /** `true` when ambiguous characters may be included in the output. * `false` when ambiguous characters should not be included in the output. */ - ambiguous: boolean; + ambiguous?: boolean; /** `true` when uppercase ASCII characters should be included in the output * This value defaults to `false. */ - uppercase: boolean; + uppercase?: boolean; /** The minimum number of uppercase characters to include in the output. * The value is ignored when `uppercase` is `false`. * The value defaults to 1 when `uppercase` is `true`. */ - minUppercase: number; + minUppercase?: number; /** `true` when lowercase ASCII characters should be included in the output. * This value defaults to `false`. */ - lowercase: boolean; + lowercase?: boolean; /** The minimum number of lowercase characters to include in the output. * The value defaults to 1 when `lowercase` is `true`. * The value defaults to 0 when `lowercase` is `false`. */ - minLowercase: number; + minLowercase?: number; /** Whether or not to include ASCII digits in the output * This value defaults to `true` when `minNumber` is at least 1. * This value defaults to `false` when `minNumber` is less than 1. */ - number: boolean; + number?: boolean; /** The minimum number of digits to include in the output. * The value defaults to 1 when `number` is `true`. * The value defaults to 0 when `number` is `false`. */ - minNumber: number; + minNumber?: number; /** Whether or not to include special characters in the output. * This value defaults to `true` when `minSpecial` is at least 1. * This value defaults to `false` when `minSpecial` is less than 1. */ - special: boolean; + special?: boolean; /** The minimum number of special characters to include in the output. * This value defaults to 1 when `special` is `true`. * This value defaults to 0 when `special` is `false`. */ - minSpecial: number; + minSpecial?: number; }; /** Request format for password credential generation. diff --git a/libs/tools/generator/core/src/util.ts b/libs/tools/generator/core/src/util.ts index 98c2e8ab283..4b6041ffeba 100644 --- a/libs/tools/generator/core/src/util.ts +++ b/libs/tools/generator/core/src/util.ts @@ -107,7 +107,7 @@ export function optionsToRandomAsciiRequest(options: PasswordGenerationOptions) DefaultPasswordGenerationOptions.special, DefaultPasswordGenerationOptions.minSpecial, ), - ambiguous: options.ambiguous ?? DefaultPasswordGenerationOptions.ambiguous, + ambiguous: options.ambiguous ?? DefaultPasswordGenerationOptions.ambiguous!, all: 0, }; From 0fce8e2726e5b5665b573c45cb5219ab9c55131a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:05:44 +0100 Subject: [PATCH 217/270] [deps] SM: Update eslint-plugin-storybook to v0.11.2 (#11322) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 282 ++++++++++------------------------------------ package.json | 2 +- 2 files changed, 62 insertions(+), 222 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b30fedbd06..bf724bae691 100644 --- a/package-lock.json +++ b/package-lock.json @@ -146,7 +146,7 @@ "eslint-plugin-import": "2.29.1", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", - "eslint-plugin-storybook": "0.8.0", + "eslint-plugin-storybook": "0.11.2", "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", @@ -10210,19 +10210,6 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/@typescript-eslint/experimental-utils": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", @@ -10456,56 +10443,42 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz", - "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz", - "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10513,14 +10486,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz", - "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10528,54 +10500,63 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz", - "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz", - "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.20.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.19.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", @@ -16382,161 +16363,21 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.8.0.tgz", - "integrity": "sha512-CZeVO5EzmPY7qghO2t64oaFM+8FTaD4uzOEjHKp516exyTKo+skKAL9GI3QALS2BXhyALJjNtwbmr1XinGE8bA==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.11.2.tgz", + "integrity": "sha512-0Z4DUklJrC+GHjCRXa7PYfPzWC15DaVnwaOYenpgXiCEijXPZkLKCms+rHhtoRcWccP7Z8DpOOaP1gc3P9oOwg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf": "^0.0.1", - "@typescript-eslint/utils": "^5.62.0", - "requireindex": "^1.2.0", + "@storybook/csf": "^0.1.11", + "@typescript-eslint/utils": "^8.8.1", "ts-dedent": "^2.2.0" }, "engines": { "node": ">= 18" }, "peerDependencies": { - "eslint": ">=6" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@storybook/csf": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", - "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "eslint": ">=8" } }, "node_modules/eslint-plugin-tailwindcss": { @@ -30908,17 +30749,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", - "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-dedent": { diff --git a/package.json b/package.json index 5d7c2ace64d..c4248bcdbab 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "eslint-plugin-import": "2.29.1", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", - "eslint-plugin-storybook": "0.8.0", + "eslint-plugin-storybook": "0.11.2", "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", From 918c68a9f643fdf4e031620431190b21448d973d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:08:22 +0100 Subject: [PATCH 218/270] [deps] SM: Update eslint-plugin-import to v2.31.0 (#11066) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 35 ++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf724bae691..9f1acd6b679 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,7 +143,7 @@ "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", - "eslint-plugin-import": "2.29.1", + "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.11.2", @@ -7990,6 +7990,13 @@ "darwin" ] }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@schematics/angular": { "version": "18.2.12", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.12.tgz", @@ -16234,35 +16241,37 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { diff --git a/package.json b/package.json index c4248bcdbab..2cd4d7b4706 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", - "eslint-plugin-import": "2.29.1", + "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.11.2", From 9a6f00ef119c92c10d6c6488b7ef5bc1db84cf51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:24:46 +0100 Subject: [PATCH 219/270] [deps] SM: Update eslint-import-resolver-typescript to v3.7.0 (#10845) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 175 ++++++++++++++-------------------------------- package.json | 2 +- 2 files changed, 53 insertions(+), 124 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f1acd6b679..604de469007 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,7 +142,7 @@ "electron-updater": "6.3.9", "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", - "eslint-import-resolver-typescript": "3.6.1", + "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", @@ -950,18 +950,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -1324,18 +1312,6 @@ "node": ">= 4" } }, - "node_modules/@angular-eslint/schematics/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular-eslint/template-parser": { "version": "18.4.3", "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.3.tgz", @@ -1853,18 +1829,6 @@ "node": ">=14.0.0" } }, - "node_modules/@angular/build/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular/build/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -2060,18 +2024,6 @@ "node": ">=18.0.0" } }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular/cli/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -5042,19 +4994,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@compodoc/compodoc/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@compodoc/compodoc/node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -7499,6 +7438,16 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/agent": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", @@ -8864,19 +8813,6 @@ "storybook": "^8.4.7" } }, - "node_modules/@storybook/core/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@storybook/csf": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.11.tgz", @@ -15610,19 +15546,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/electron-updater/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/electron/node_modules/@types/node": { "version": "20.17.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz", @@ -16187,19 +16110,20 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", - "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", + "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", "dev": true, "license": "ISC", "dependencies": { - "debug": "^4.3.4", - "enhanced-resolve": "^5.12.0", - "eslint-module-utils": "^2.7.4", - "fast-glob": "^3.3.1", - "get-tsconfig": "^4.5.0", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3" + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.7", + "enhanced-resolve": "^5.15.0", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3", + "stable-hash": "^0.0.4" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -16209,7 +16133,16 @@ }, "peerDependencies": { "eslint": "*", - "eslint-plugin-import": "*" + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } } }, "node_modules/eslint-module-utils": { @@ -19354,6 +19287,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", + "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -28852,13 +28795,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -28874,24 +28814,6 @@ "license": "MIT", "optional": true }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/send": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", @@ -29613,6 +29535,13 @@ "dev": true, "license": "ISC" }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", + "dev": true, + "license": "MIT" + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", diff --git a/package.json b/package.json index 2cd4d7b4706..2b2abb8d0bf 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "electron-updater": "6.3.9", "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", - "eslint-import-resolver-typescript": "3.6.1", + "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", From 7c89c520987b3845896408d11122291ae11341a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 16 Jan 2025 16:50:38 +0100 Subject: [PATCH 220/270] Dynamically load MacOS passkey (#12897) --- .../objc/src/native/autofill/commands/sync.m | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m index 8b73635a7ca..fc13c04591a 100644 --- a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m @@ -30,21 +30,24 @@ void runSync(void* context, NSDictionary *params) { [mappedCredentials addObject:credential]; } - if ([type isEqualToString:@"fido2"]) { - NSString *cipherId = credential[@"cipherId"]; - NSString *rpId = credential[@"rpId"]; - NSString *userName = credential[@"userName"]; - NSData *credentialId = decodeBase64URL(credential[@"credentialId"]); - NSData *userHandle = decodeBase64URL(credential[@"userHandle"]); + if (@available(macos 14, *)) { + if ([type isEqualToString:@"fido2"]) { + NSString *cipherId = credential[@"cipherId"]; + NSString *rpId = credential[@"rpId"]; + NSString *userName = credential[@"userName"]; + NSData *credentialId = decodeBase64URL(credential[@"credentialId"]); + NSData *userHandle = decodeBase64URL(credential[@"userHandle"]); - ASPasskeyCredentialIdentity *credential = [[ASPasskeyCredentialIdentity alloc] - initWithRelyingPartyIdentifier:rpId - userName:userName - credentialID:credentialId - userHandle:userHandle - recordIdentifier:cipherId]; + Class passkeyCredentialIdentityClass = NSClassFromString(@"ASPasskeyCredentialIdentity"); + id credential = [[passkeyCredentialIdentityClass alloc] + initWithRelyingPartyIdentifier:rpId + userName:userName + credentialID:credentialId + userHandle:userHandle + recordIdentifier:cipherId]; - [mappedCredentials addObject:credential]; + [mappedCredentials addObject:credential]; + } } } From ca9bb52a899d1feda8ed8c6db3c62e4bc873fab4 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 16 Jan 2025 17:12:26 +0100 Subject: [PATCH 221/270] Move linting dependencies to architecture (#12910) --- .github/renovate.json | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 150ac1ac99d..b5c43cc1d39 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -69,6 +69,29 @@ "commitMessagePrefix": "[deps] Auth:", "reviewers": ["team:team-auth-dev"] }, + { + "matchPackageNames": [ + "@angular-eslint/eslint-plugin-template", + "@angular-eslint/eslint-plugin", + "@angular-eslint/schematics", + "@angular-eslint/template-parser", + "@typescript-eslint/eslint-plugin", + "@typescript-eslint/parser", + "eslint-config-prettier", + "eslint-import-resolver-typescript", + "eslint-plugin-import", + "eslint-plugin-rxjs-angular", + "eslint-plugin-rxjs", + "eslint-plugin-storybook", + "eslint-plugin-tailwindcss", + "eslint", + "husky", + "lint-staged" + ], + "description": "Architecture owned dependencies", + "commitMessagePrefix": "[deps] Architecture:", + "reviewers": ["team:dept-architecture"] + }, { "matchPackageNames": [ "@emotion/css", @@ -190,28 +213,11 @@ }, { "matchPackageNames": [ - "@angular-eslint/eslint-plugin", - "@angular-eslint/eslint-plugin-template", - "@angular-eslint/schematics", - "@angular-eslint/template-parser", - "@angular/elements", "@types/jest", - "@typescript-eslint/eslint-plugin", - "@typescript-eslint/parser", - "eslint", - "eslint-config-prettier", - "eslint-import-resolver-typescript", - "eslint-plugin-import", - "eslint-plugin-rxjs", - "eslint-plugin-rxjs-angular", - "eslint-plugin-storybook", - "eslint-plugin-tailwindcss", - "husky", "jest-junit", "jest-mock-extended", "jest-preset-angular", "jest-diff", - "lint-staged", "ts-jest" ], "description": "Secrets Manager owned dependencies", From 97ee050e5dd9701cf9facbbf31fad160b6f066d2 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 16 Jan 2025 17:49:20 +0100 Subject: [PATCH 222/270] [PM-17113] Fix system authentication setup (#12907) * Fix system authentication setup * Fix biometric status * Remove debug log * Fix tests --- .../src/app/accounts/settings.component.ts | 18 ++++++++++++++---- .../biometrics/biometrics.service.spec.ts | 10 +++++----- .../biometrics/main-biometrics.service.ts | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index f3440975cf2..d35845fa6aa 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -362,14 +362,24 @@ export class SettingsComponent implements OnInit, OnDestroy { } }); - this.supportsBiometric = - (await this.biometricsService.getBiometricsStatus()) === BiometricsStatus.Available; + this.supportsBiometric = this.shouldAllowBiometricSetup( + await this.biometricsService.getBiometricsStatus(), + ); this.timerId = setInterval(async () => { - this.supportsBiometric = - (await this.biometricsService.getBiometricsStatus()) === BiometricsStatus.Available; + this.supportsBiometric = this.shouldAllowBiometricSetup( + await this.biometricsService.getBiometricsStatus(), + ); }, 1000); } + private shouldAllowBiometricSetup(biometricStatus: BiometricsStatus): boolean { + return [ + BiometricsStatus.Available, + BiometricsStatus.AutoSetupNeeded, + BiometricsStatus.ManualSetupNeeded, + ].includes(biometricStatus); + } + async saveVaultTimeout(newValue: VaultTimeout) { if (newValue === VaultTimeoutStringType.Never) { const confirmed = await this.dialogService.openSimpleDialog({ diff --git a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts index e69ebca3630..9e5755dd579 100644 --- a/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts +++ b/apps/desktop/src/key-management/biometrics/biometrics.service.spec.ts @@ -117,15 +117,15 @@ describe("biometrics tests", function () { const testCases = [ // happy path [true, false, false, BiometricsStatus.Available], - [false, true, true, BiometricsStatus.AutoSetupNeeded], - [false, true, false, BiometricsStatus.ManualSetupNeeded], - [false, false, false, BiometricsStatus.HardwareUnavailable], + [false, true, true, BiometricsStatus.HardwareUnavailable], + [true, true, true, BiometricsStatus.AutoSetupNeeded], + [true, true, false, BiometricsStatus.ManualSetupNeeded], // should not happen [false, false, true, BiometricsStatus.HardwareUnavailable], - [true, true, true, BiometricsStatus.Available], - [true, true, false, BiometricsStatus.Available], [true, false, true, BiometricsStatus.Available], + [false, true, false, BiometricsStatus.HardwareUnavailable], + [false, false, false, BiometricsStatus.HardwareUnavailable], ]; for (const [supportsBiometric, needsSetup, canAutoSetup, expected] of testCases) { diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts index 06956503a05..d0ba66fdad4 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -60,6 +60,8 @@ export class MainBiometricsService extends DesktopBiometricsService { */ async getBiometricsStatus(): Promise { if (!(await this.osBiometricsService.osSupportsBiometric())) { + return BiometricsStatus.HardwareUnavailable; + } else { if (await this.osBiometricsService.osBiometricsNeedsSetup()) { if (await this.osBiometricsService.osBiometricsCanAutoSetup()) { return BiometricsStatus.AutoSetupNeeded; @@ -67,8 +69,6 @@ export class MainBiometricsService extends DesktopBiometricsService { return BiometricsStatus.ManualSetupNeeded; } } - - return BiometricsStatus.HardwareUnavailable; } return BiometricsStatus.Available; } From e815f89b9993d56d8c4c3f4f13cb96d0c5877bd8 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Thu, 16 Jan 2025 11:07:16 -0600 Subject: [PATCH 223/270] PM-17173 minor style changes (#12913) --- .../org-at-risk-members-dialog.component.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html index 41ac8af7886..1f1de103661 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html @@ -4,9 +4,7 @@
    - {{ - "atRiskMembersDescription" | i18n - }} + {{ "atRiskMembersDescription" | i18n }}
    {{ "email" | i18n }}
    {{ "atRiskPasswords" | i18n }}
    From 772196c6e32ea496d11518e8830338d559030650 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:44:58 +0000 Subject: [PATCH 224/270] [deps] SM: Update typescript-eslint monorepo to v8.20.0 (#10583) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 222 +++++++++++----------------------------------- package.json | 4 +- 2 files changed, 56 insertions(+), 170 deletions(-) diff --git a/package-lock.json b/package-lock.json index 604de469007..12a1468e3b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,8 +121,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "8.19.1", - "@typescript-eslint/parser": "8.19.1", + "@typescript-eslint/eslint-plugin": "8.20.0", + "@typescript-eslint/parser": "8.20.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", @@ -10076,17 +10076,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", - "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", + "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.19.1", - "@typescript-eslint/type-utils": "8.19.1", - "@typescript-eslint/utils": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/type-utils": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -10105,54 +10105,6 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", - "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.19.1", - "@typescript-eslint/utils": "8.19.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", - "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.19.1", - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/typescript-estree": "8.19.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" - } - }, "node_modules/@typescript-eslint/experimental-utils": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", @@ -10303,16 +10255,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", - "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", + "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.19.1", - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/typescript-estree": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1", + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4" }, "engines": { @@ -10328,14 +10280,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", - "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10345,10 +10297,34 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", + "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.20.0", + "@typescript-eslint/utils": "8.20.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", - "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", "engines": { @@ -10360,14 +10336,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", - "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.1", - "@typescript-eslint/visitor-keys": "8.19.1", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -10410,66 +10386,7 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", - "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.20.0", - "@typescript-eslint/visitor-keys": "8.20.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", - "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", - "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.20.0", - "@typescript-eslint/visitor-keys": "8.20.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "node_modules/@typescript-eslint/visitor-keys": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", @@ -10487,37 +10404,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", - "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.19.1", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", diff --git a/package.json b/package.json index 2b2abb8d0bf..8b692a57ac9 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "8.19.1", - "@typescript-eslint/parser": "8.19.1", + "@typescript-eslint/eslint-plugin": "8.20.0", + "@typescript-eslint/parser": "8.20.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", From f82b8ca844cd7602d9a8257bd26a8c8f0b57ee14 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:59:18 -0800 Subject: [PATCH 225/270] feat(auth): [PM-14949] Update TDE Decryption Option Text (#12850) Update the text for the TDE decryption options under "Member decryption options". --- apps/web/src/locales/en/messages.json | 32 +++++++++---------- .../src/app/auth/sso/sso.component.html | 26 +++++++-------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index eacba623ecd..15c5a7fcf6c 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8289,33 +8289,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html index 0731820e413..036163af3d9 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html @@ -89,19 +89,19 @@ {{ "trustedDevices" | i18n }} - {{ "memberDecryptionOptionTdeDescriptionPartOne" | i18n }} - {{ - "memberDecryptionOptionTdeDescriptionLinkOne" | i18n - }} - {{ "memberDecryptionOptionTdeDescriptionPartTwo" | i18n }} - {{ - "memberDecryptionOptionTdeDescriptionLinkTwo" | i18n - }} - {{ "memberDecryptionOptionTdeDescriptionPartThree" | i18n }} - {{ - "memberDecryptionOptionTdeDescriptionLinkThree" | i18n - }} - {{ "memberDecryptionOptionTdeDescriptionPartFour" | i18n }} + {{ "memberDecryptionOptionTdeDescPart1" | i18n }} + + {{ "memberDecryptionOptionTdeDescLink1" | i18n }} + + {{ "memberDecryptionOptionTdeDescPart2" | i18n }} + + {{ "memberDecryptionOptionTdeDescLink2" | i18n }} + + {{ "memberDecryptionOptionTdeDescPart3" | i18n }} + + {{ "memberDecryptionOptionTdeDescLink3" | i18n }} + + {{ "memberDecryptionOptionTdeDescPart4" | i18n }} From 70cf4593fbffb7b7ca1c6e7828f6533c3a07fbd5 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:10:23 -0800 Subject: [PATCH 226/270] show login credentials if only passkey is present (#12870) --- libs/vault/src/cipher-view/cipher-view.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 4bd87a7869d..f872ad0cf15 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -101,8 +101,8 @@ export class CipherViewComponent implements OnChanges, OnDestroy { return false; } - const { username, password, totp } = this.cipher.login; - return username || password || totp; + const { username, password, totp, fido2Credentials } = this.cipher.login; + return username || password || totp || fido2Credentials; } get hasAutofill() { From 3917f50fdde96dbe0c746a3f0e956e115756096c Mon Sep 17 00:00:00 2001 From: Andy Pixley <3723676+pixman20@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:20:03 -0500 Subject: [PATCH 227/270] [BRE-560] Display rollout percentage in run name (#12919) --- .github/workflows/staged-rollout-desktop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/staged-rollout-desktop.yml b/.github/workflows/staged-rollout-desktop.yml index 91250a443f2..4ec3af3be97 100644 --- a/.github/workflows/staged-rollout-desktop.yml +++ b/.github/workflows/staged-rollout-desktop.yml @@ -1,4 +1,5 @@ name: Staged Rollout Desktop +run-name: Staged Rollout Desktop - ${{ inputs.rollout_percentage }}% on: workflow_dispatch: From ea052b9e079dc95daa799dc87c7394c0473649d8 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Thu, 16 Jan 2025 15:43:04 -0500 Subject: [PATCH 228/270] [CL-428] create drawer component (#12812) * remove private/protected/lifecycle fields from Storybook docs table * move theme override decorator into util method * implement base drawer component * update bit-layout to be drawer container * create drawer helper components * expose new APIs to DS barrel file * write docs * update docs; add role input * use host directive instead of service * clean up logic a tad * add start slot to story * update docs * Apply suggestions from code review Co-authored-by: Victoria League * update docs * Update libs/components/src/drawer/drawer.mdx Co-authored-by: Victoria League * update docs / stories * add non text element to drawer --------- Co-authored-by: Victoria League --- angular.json | 8 ++ .../src/drawer/drawer-body.component.ts | 36 +++++ .../src/drawer/drawer-close.directive.ts | 29 ++++ .../src/drawer/drawer-header.component.html | 15 +++ .../src/drawer/drawer-header.component.ts | 34 +++++ .../src/drawer/drawer-host.directive.ts | 28 ++++ .../src/drawer/drawer.component.html | 8 ++ .../components/src/drawer/drawer.component.ts | 76 +++++++++++ libs/components/src/drawer/drawer.mdx | 120 +++++++++++++++++ libs/components/src/drawer/drawer.module.ts | 12 ++ libs/components/src/drawer/drawer.stories.ts | 124 ++++++++++++++++++ libs/components/src/drawer/index.ts | 5 + libs/components/src/index.ts | 1 + .../src/layout/layout.component.html | 1 + .../components/src/layout/layout.component.ts | 10 +- libs/components/src/layout/layout.stories.ts | 10 +- libs/components/src/layout/mocks.ts | 7 + .../src/navigation/nav-group.stories.ts | 2 +- .../src/navigation/nav-item.stories.ts | 2 +- .../components/kitchen-sink-main.component.ts | 97 +++++++++++--- .../kitchen-sink-shared.module.ts | 3 + .../kitchen-sink/kitchen-sink.stories.ts | 45 +++---- .../storybook-decorators.ts} | 16 ++- 23 files changed, 634 insertions(+), 55 deletions(-) create mode 100644 libs/components/src/drawer/drawer-body.component.ts create mode 100644 libs/components/src/drawer/drawer-close.directive.ts create mode 100644 libs/components/src/drawer/drawer-header.component.html create mode 100644 libs/components/src/drawer/drawer-header.component.ts create mode 100644 libs/components/src/drawer/drawer-host.directive.ts create mode 100644 libs/components/src/drawer/drawer.component.html create mode 100644 libs/components/src/drawer/drawer.component.ts create mode 100644 libs/components/src/drawer/drawer.mdx create mode 100644 libs/components/src/drawer/drawer.module.ts create mode 100644 libs/components/src/drawer/drawer.stories.ts create mode 100644 libs/components/src/drawer/index.ts create mode 100644 libs/components/src/layout/mocks.ts rename libs/components/src/{utils/position-fixed-wrapper-decorator.ts => stories/storybook-decorators.ts} (50%) diff --git a/angular.json b/angular.json index 7053050262e..665d810cf4e 100644 --- a/angular.json +++ b/angular.json @@ -147,6 +147,10 @@ "./tsconfig.json", "-e", "json", + "--disableInternal", + "--disableLifeCycleHooks", + "--disablePrivate", + "--disableProtected", "-d", ".", "--disableRoutesGraph" @@ -165,6 +169,10 @@ "./tsconfig.json", "-e", "json", + "--disableInternal", + "--disableLifeCycleHooks", + "--disablePrivate", + "--disableProtected", "-d", ".", "--disableRoutesGraph" diff --git a/libs/components/src/drawer/drawer-body.component.ts b/libs/components/src/drawer/drawer-body.component.ts new file mode 100644 index 00000000000..9bd2adcffbc --- /dev/null +++ b/libs/components/src/drawer/drawer-body.component.ts @@ -0,0 +1,36 @@ +import { CdkScrollable } from "@angular/cdk/scrolling"; +import { ChangeDetectionStrategy, Component, Signal, inject } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; + +/** + * Body container for `bit-drawer` + */ +@Component({ + selector: "bit-drawer-body", + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [], + host: { + class: + "tw-p-4 tw-pt-0 tw-block tw-overflow-auto tw-border-solid tw-border tw-border-transparent tw-transition-colors tw-duration-200", + "[class.tw-border-t-secondary-300]": "isScrolled()", + }, + hostDirectives: [ + { + directive: CdkScrollable, + }, + ], + template: ` `, +}) +export class DrawerBodyComponent { + private scrollable = inject(CdkScrollable); + + /** TODO: share this utility with browser popup header? */ + protected isScrolled: Signal = toSignal( + this.scrollable + .elementScrolled() + .pipe(map(() => this.scrollable.measureScrollOffset("top") > 0)), + { initialValue: false }, + ); +} diff --git a/libs/components/src/drawer/drawer-close.directive.ts b/libs/components/src/drawer/drawer-close.directive.ts new file mode 100644 index 00000000000..bf56dd8b71f --- /dev/null +++ b/libs/components/src/drawer/drawer-close.directive.ts @@ -0,0 +1,29 @@ +import { Directive, inject } from "@angular/core"; + +import { DrawerComponent } from "./drawer.component"; + +/** + * Closes the ancestor drawer + * + * @example + * + * ```html + * + * + * + * ``` + **/ +@Directive({ + selector: "button[bitDrawerClose]", + standalone: true, + host: { + "(click)": "onClick()", + }, +}) +export class DrawerCloseDirective { + private drawer = inject(DrawerComponent, { optional: true }); + + protected onClick() { + this.drawer?.open.set(false); + } +} diff --git a/libs/components/src/drawer/drawer-header.component.html b/libs/components/src/drawer/drawer-header.component.html new file mode 100644 index 00000000000..4652e5537ee --- /dev/null +++ b/libs/components/src/drawer/drawer-header.component.html @@ -0,0 +1,15 @@ +
    +
    + +

    + {{ title() }} +

    +
    + +
    diff --git a/libs/components/src/drawer/drawer-header.component.ts b/libs/components/src/drawer/drawer-header.component.ts new file mode 100644 index 00000000000..73834b8487e --- /dev/null +++ b/libs/components/src/drawer/drawer-header.component.ts @@ -0,0 +1,34 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, HostBinding, input } from "@angular/core"; + +import { IconButtonModule } from "../icon-button"; +import { I18nPipe } from "../shared/i18n.pipe"; +import { TypographyModule } from "../typography"; + +import { DrawerCloseDirective } from "./drawer-close.directive"; + +/** + * Header container for `bit-drawer` + **/ +@Component({ + selector: "bit-drawer-header", + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, DrawerCloseDirective, TypographyModule, IconButtonModule, I18nPipe], + templateUrl: "drawer-header.component.html", + host: { + class: "tw-block tw-pl-4 tw-pr-2 tw-py-2", + }, +}) +export class DrawerHeaderComponent { + /** + * The title to display + */ + title = input.required(); + + /** We don't want to set the HTML title attribute with `this.title` */ + @HostBinding("attr.title") + protected get getTitle(): null { + return null; + } +} diff --git a/libs/components/src/drawer/drawer-host.directive.ts b/libs/components/src/drawer/drawer-host.directive.ts new file mode 100644 index 00000000000..f5e3e56b099 --- /dev/null +++ b/libs/components/src/drawer/drawer-host.directive.ts @@ -0,0 +1,28 @@ +import { Portal } from "@angular/cdk/portal"; +import { Directive, signal } from "@angular/core"; + +/** + * Host that renders a drawer + * + * @internal + */ +@Directive({ + selector: "[bitDrawerHost]", + standalone: true, +}) +export class DrawerHostDirective { + private _portal = signal | undefined>(undefined); + + /** The portal to display */ + portal = this._portal.asReadonly(); + + open(portal: Portal) { + this._portal.set(portal); + } + + close(portal: Portal) { + if (portal === this.portal()) { + this._portal.set(undefined); + } + } +} diff --git a/libs/components/src/drawer/drawer.component.html b/libs/components/src/drawer/drawer.component.html new file mode 100644 index 00000000000..fce6b3c57eb --- /dev/null +++ b/libs/components/src/drawer/drawer.component.html @@ -0,0 +1,8 @@ + +
    + +
    +
    diff --git a/libs/components/src/drawer/drawer.component.ts b/libs/components/src/drawer/drawer.component.ts new file mode 100644 index 00000000000..ccabb6f0b6e --- /dev/null +++ b/libs/components/src/drawer/drawer.component.ts @@ -0,0 +1,76 @@ +import { CdkPortal, PortalModule } from "@angular/cdk/portal"; +import { CommonModule } from "@angular/common"; +import { + ChangeDetectionStrategy, + Component, + effect, + inject, + input, + model, + viewChild, +} from "@angular/core"; + +import { DrawerHostDirective } from "./drawer-host.directive"; + +/** + * A drawer is a panel of supplementary content that is adjacent to the page's main content. + * + * Drawers render in `bit-layout`. Drawers must be a descendant of `bit-layout`, but they do not need to be a direct descendant. + */ +@Component({ + selector: "bit-drawer", + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, PortalModule], + templateUrl: "drawer.component.html", +}) +export class DrawerComponent { + private drawerHost = inject(DrawerHostDirective); + private portal = viewChild.required(CdkPortal); + + /** + * Whether or not the drawer is open. + * + * Note: Does not support implicit boolean transform due to Angular limitation. Must be bound explicitly `[open]="true"` instead of just `open`. + * https://github.com/angular/angular/issues/55166#issuecomment-2032150999 + **/ + open = model(false); + + /** + * The ARIA role of the drawer. + * + * - [complementary](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/complementary_role) + * - For drawers that contain content that is complementary to the page's main content. (default) + * - [navigation](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/navigation_role) + * - For drawers that primary contain links to other content. + */ + role = input<"complementary" | "navigation">("complementary"); + + constructor() { + effect( + () => { + this.open() ? this.drawerHost.open(this.portal()) : this.drawerHost.close(this.portal()); + }, + { + allowSignalWrites: true, + }, + ); + + // Set `open` to `false` when another drawer is opened. + effect( + () => { + if (this.drawerHost.portal() !== this.portal()) { + this.open.set(false); + } + }, + { + allowSignalWrites: true, + }, + ); + } + + /** Toggle the drawer between open & closed */ + toggle() { + this.open.update((prev) => !prev); + } +} diff --git a/libs/components/src/drawer/drawer.mdx b/libs/components/src/drawer/drawer.mdx new file mode 100644 index 00000000000..0098ce64ea9 --- /dev/null +++ b/libs/components/src/drawer/drawer.mdx @@ -0,0 +1,120 @@ +import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; + +import * as stories from "./drawer.stories"; + +import { DrawerOpen as KitchenSink } from "../stories/kitchen-sink/kitchen-sink.stories"; + + + +```ts +import { DrawerComponent } from "@bitwarden/components"; +``` + +# Drawer + +A drawer is a panel of supplementary content that is adjacent to the page's main content. + + + + + +## Usage + +A `bit-drawer` in a template will not render inline, but rather will render adjacent to the main +page content. + +```html + + + +

    Lorem ipsum dolor...

    +
    +
    +``` + +`bit-drawer` must be a descendant of `bit-layout`, but it does not need to be a direct descendant. + +## Header and body + +Header and body content can be provided with the `bit-drawer-header` and `bit-drawer-body` +components, respectively. + +A title can be passed to the header by input: +`` + +Custom content can be rendered before the title with the header's `start` slot: + +```html + + + +``` + +## Opening and closing + +`bit-drawer` opens when its `open` input is `true`: + +```html +... +``` + +Note: Model inputs do not support implicit boolean transformation (see Angular reasoning +[here](https://github.com/angular/angular/issues/55166#issuecomment-2032150999)). `open` must be +bound explicitly `` instead of just ``. + +Buttons can be made to open/toggle drawers by referencing a template variable, or by manipulating +state that is bound to `open`: + +```html + ... +``` + +For convenience, close buttons can be created _inside_ the drawer with the `bitDrawerClose` +directive: + +```html + + + +``` + +## Multiple Drawers + +Only one drawer can be open at a time, and they do not stack. If a drawer is already open, opening +another will close and replace the one already open. + + + +## Headless + +Omitting `bit-drawer-header` and `bit-drawer-body` allows for fully customizable content. + + + +## Accessibility + +- The drawer should contain an h2 element. If you are using `bit-drawer-header`, this is created for + you via the `title` input: + +```html + +

    Hello world!

    +
    + + + + + + +``` + +- The ARIA role of the drawer can be set with the `role` attribute: + - [complementary](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/complementary_role) + (default) + - For drawers that contain content that is complementary to the page's main content. + - [navigation](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/navigation_role) + - For drawers that primary contain links to other content. + +## Kitchen Sink + + diff --git a/libs/components/src/drawer/drawer.module.ts b/libs/components/src/drawer/drawer.module.ts new file mode 100644 index 00000000000..9f51ba06b4e --- /dev/null +++ b/libs/components/src/drawer/drawer.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from "@angular/core"; + +import { DrawerBodyComponent } from "./drawer-body.component"; +import { DrawerCloseDirective } from "./drawer-close.directive"; +import { DrawerHeaderComponent } from "./drawer-header.component"; +import { DrawerComponent } from "./drawer.component"; + +@NgModule({ + imports: [DrawerComponent, DrawerHeaderComponent, DrawerBodyComponent, DrawerCloseDirective], + exports: [DrawerComponent, DrawerHeaderComponent, DrawerBodyComponent, DrawerCloseDirective], +}) +export class DrawerModule {} diff --git a/libs/components/src/drawer/drawer.stories.ts b/libs/components/src/drawer/drawer.stories.ts new file mode 100644 index 00000000000..54b4c89f4ce --- /dev/null +++ b/libs/components/src/drawer/drawer.stories.ts @@ -0,0 +1,124 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { RouterTestingModule } from "@angular/router/testing"; +import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { ButtonModule } from "../button"; +import { CalloutModule } from "../callout"; +import { LayoutComponent } from "../layout"; +import { mockLayoutI18n } from "../layout/mocks"; +import { + disableBothThemeDecorator, + positionFixedWrapperDecorator, +} from "../stories/storybook-decorators"; +import { TypographyModule } from "../typography"; +import { I18nMockService } from "../utils"; + +import { DrawerBodyComponent } from "./drawer-body.component"; +import { DrawerHeaderComponent } from "./drawer-header.component"; +import { DrawerComponent } from "./drawer.component"; +import { DrawerModule } from "./drawer.module"; + +export default { + title: "Component Library/Drawer", + component: DrawerComponent, + subcomponents: { + DrawerHeaderComponent, + DrawerBodyComponent, + }, + decorators: [ + positionFixedWrapperDecorator(), + disableBothThemeDecorator, + moduleMetadata({ + imports: [ + RouterTestingModule, + LayoutComponent, + DrawerModule, + ButtonModule, + CalloutModule, + TypographyModule, + ], + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + ...mockLayoutI18n, + close: "Close", + }); + }, + }, + ], + }), + ], +} as Meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + +

    The drawer is {{ open ? "open" : "closed" }}.

    + + + + + + + + +

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

    + +
    + + `, + }), + args: { + open: true, + }, +}; + +export const Headless: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + +

    The drawer is {{ open ? "open" : "closed" }}.

    + + +

    + Hello world! +
    + + `, + }), + args: { + open: true, + }, +}; + +export const MultipleDrawers: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + + + Foo + + + + Bar + + + `, + }), +}; diff --git a/libs/components/src/drawer/index.ts b/libs/components/src/drawer/index.ts new file mode 100644 index 00000000000..abf5b8d34f1 --- /dev/null +++ b/libs/components/src/drawer/index.ts @@ -0,0 +1,5 @@ +export * from "./drawer.module"; +export * from "./drawer.component"; +export * from "./drawer-body.component"; +export * from "./drawer-close.directive"; +export * from "./drawer-header.component"; diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index a48750a99ff..ed844520444 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -14,6 +14,7 @@ export * from "./color-password"; export * from "./container"; export * from "./dialog"; export * from "./disclosure"; +export * from "./drawer"; export * from "./form-field"; export * from "./icon-button"; export * from "./icon"; diff --git a/libs/components/src/layout/layout.component.html b/libs/components/src/layout/layout.component.html index ccbb40c2b57..7c1c5b2501d 100644 --- a/libs/components/src/layout/layout.component.html +++ b/libs/components/src/layout/layout.component.html @@ -37,4 +37,5 @@ >
    + diff --git a/libs/components/src/layout/layout.component.ts b/libs/components/src/layout/layout.component.ts index d55ad8493eb..7bf8a6ad173 100644 --- a/libs/components/src/layout/layout.component.ts +++ b/libs/components/src/layout/layout.component.ts @@ -1,7 +1,9 @@ +import { PortalModule } from "@angular/cdk/portal"; import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, inject } from "@angular/core"; import { RouterModule } from "@angular/router"; +import { DrawerHostDirective } from "../drawer/drawer-host.directive"; import { LinkModule } from "../link"; import { SideNavService } from "../navigation/side-nav.service"; import { SharedModule } from "../shared"; @@ -10,12 +12,14 @@ import { SharedModule } from "../shared"; selector: "bit-layout", templateUrl: "layout.component.html", standalone: true, - imports: [CommonModule, SharedModule, LinkModule, RouterModule], + imports: [CommonModule, SharedModule, LinkModule, RouterModule, PortalModule], + hostDirectives: [DrawerHostDirective], }) export class LayoutComponent { protected mainContentId = "main-content"; - constructor(protected sideNavService: SideNavService) {} + protected sideNavService = inject(SideNavService); + protected drawerPortal = inject(DrawerHostDirective).portal; focusMainContent() { document.getElementById(this.mainContentId)?.focus(); diff --git a/libs/components/src/layout/layout.stories.ts b/libs/components/src/layout/layout.stories.ts index a0eadebe7fa..7fdad655548 100644 --- a/libs/components/src/layout/layout.stories.ts +++ b/libs/components/src/layout/layout.stories.ts @@ -6,10 +6,11 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { CalloutModule } from "../callout"; import { NavigationModule } from "../navigation"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; -import { positionFixedWrapperDecorator } from "../utils/position-fixed-wrapper-decorator"; import { LayoutComponent } from "./layout.component"; +import { mockLayoutI18n } from "./mocks"; export default { title: "Component Library/Layout", @@ -22,12 +23,7 @@ export default { { provide: I18nService, useFactory: () => { - return new I18nMockService({ - toggleSideNavigation: "Toggle side navigation", - skipToContent: "Skip to content", - submenu: "submenu", - toggleCollapse: "toggle collapse", - }); + return new I18nMockService(mockLayoutI18n); }, }, ], diff --git a/libs/components/src/layout/mocks.ts b/libs/components/src/layout/mocks.ts new file mode 100644 index 00000000000..50c2bd9afb2 --- /dev/null +++ b/libs/components/src/layout/mocks.ts @@ -0,0 +1,7 @@ +/** TODO: create mock messages.json file for all of CL in favor of sharing per-Story mocks */ +export const mockLayoutI18n = { + toggleSideNavigation: "Toggle side navigation", + skipToContent: "Skip to content", + submenu: "submenu", + toggleCollapse: "toggle collapse", +}; diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index a6fa53ff187..f412dbc20ba 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -6,8 +6,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LayoutComponent } from "../layout"; import { SharedModule } from "../shared/shared.module"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; -import { positionFixedWrapperDecorator } from "../utils/position-fixed-wrapper-decorator"; import { NavGroupComponent } from "./nav-group.component"; import { NavigationModule } from "./navigation.module"; diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 20d6ebd1d7e..376f121eb00 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -5,8 +5,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { IconButtonModule } from "../icon-button"; import { LayoutComponent } from "../layout"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; import { I18nMockService } from "../utils/i18n-mock.service"; -import { positionFixedWrapperDecorator } from "../utils/position-fixed-wrapper-decorator"; import { NavItemComponent } from "./nav-item.component"; import { NavigationModule } from "./navigation.module"; diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index 687b8917381..13f0a16a4d7 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -1,5 +1,5 @@ import { DialogRef } from "@angular/cdk/dialog"; -import { Component } from "@angular/core"; +import { Component, signal } from "@angular/core"; import { DialogService } from "../../../dialog"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; @@ -28,13 +28,7 @@ class KitchenSinkDialog { @Component({ standalone: true, selector: "bit-tab-main", - imports: [ - KitchenSinkSharedModule, - KitchenSinkTable, - KitchenSinkToggleList, - KitchenSinkForm, - KitchenSinkDialog, - ], + imports: [KitchenSinkSharedModule, KitchenSinkTable, KitchenSinkToggleList, KitchenSinkForm], template: ` Kitchen Sink test zone @@ -48,6 +42,11 @@ class KitchenSinkDialog {

    +
    +

    Bitwarden Kitchen Sink

    + Learn more +
    +

    The purpose of this story is to compose together all of our components. When snapshot tests @@ -63,18 +62,14 @@ class KitchenSinkDialog {

    -
    -

    Bitwarden

    - Learn more -
    -

    About

    - + +

    Companies using Bitwarden

    @@ -99,15 +94,87 @@ class KitchenSinkDialog {
    + + + + +

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

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

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

    +

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

    +

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

    +

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

    +

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

    +

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

    +
    +
    `, }) export class KitchenSinkMainComponent { constructor(public dialogService: DialogService) {} - openDefaultDialog() { + protected drawerOpen = signal(false); + + openDialog() { this.dialogService.open(KitchenSinkDialog); } + openDrawer() { + this.drawerOpen.set(true); + } + navItems = [ { icon: "bwi-collection", name: "Password Managers", route: "/" }, { icon: "bwi-collection", name: "Favorites", route: "/" }, diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts index 56e3a92e2a3..c4fe2f9b2af 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts @@ -13,6 +13,7 @@ import { CalloutModule } from "../../callout"; import { CheckboxModule } from "../../checkbox"; import { ColorPasswordModule } from "../../color-password"; import { DialogModule } from "../../dialog"; +import { DrawerModule } from "../../drawer"; import { FormControlModule } from "../../form-control"; import { FormFieldModule } from "../../form-field"; import { IconModule } from "../../icon"; @@ -48,6 +49,7 @@ import { TypographyModule } from "../../typography"; ColorPasswordModule, CommonModule, DialogModule, + DrawerModule, FormControlModule, FormFieldModule, FormsModule, @@ -85,6 +87,7 @@ import { TypographyModule } from "../../typography"; ColorPasswordModule, CommonModule, DialogModule, + DrawerModule, FormControlModule, FormFieldModule, FormsModule, diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index a90597c1710..62b93984384 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -1,13 +1,7 @@ import { importProvidersFrom } from "@angular/core"; import { provideNoopAnimations } from "@angular/platform-browser/animations"; import { RouterModule } from "@angular/router"; -import { - Meta, - StoryObj, - applicationConfig, - componentWrapperDecorator, - moduleMetadata, -} from "@storybook/angular"; +import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; import { userEvent, getAllByRole, @@ -23,6 +17,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { DialogService } from "../../dialog"; import { LayoutComponent } from "../../layout"; import { I18nMockService } from "../../utils/i18n-mock.service"; +import { disableBothThemeDecorator, positionFixedWrapperDecorator } from "../storybook-decorators"; import { DialogVirtualScrollBlockComponent } from "./components/dialog-virtual-scroll-block.component"; import { KitchenSinkForm } from "./components/kitchen-sink-form.component"; @@ -35,25 +30,8 @@ export default { title: "Documentation / Kitchen Sink", component: LayoutComponent, decorators: [ - componentWrapperDecorator( - /** - * Applying a CSS transform makes a `position: fixed` element act like it is `position: relative` - * https://github.com/storybookjs/storybook/issues/8011#issue-490251969 - */ - (story) => { - return /* HTML */ `
    - ${story} -
    `; - }, - ({ globals }) => { - /** - * avoid a bug with the way that we render the same component twice in the same iframe and how - * that interacts with the router-outlet - */ - const themeOverride = globals["theme"] === "both" ? "light" : globals["theme"]; - return { theme: themeOverride }; - }, - ), + positionFixedWrapperDecorator(), + disableBothThemeDecorator, moduleMetadata({ imports: [ KitchenSinkSharedModule, @@ -135,7 +113,7 @@ export const MenuOpen: Story = { }, }; -export const DefaultDialogOpen: Story = { +export const DialogOpen: Story = { ...Default, play: async (context) => { const canvas = context.canvasElement; @@ -148,6 +126,19 @@ export const DefaultDialogOpen: Story = { }, }; +export const DrawerOpen: Story = { + ...Default, + play: async (context) => { + const canvas = context.canvasElement; + const drawerButton = getByRole(canvas, "button", { + name: "Open Drawer", + }); + + // workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075 + await fireEvent.click(drawerButton); + }, +}; + export const PopoverOpen: Story = { ...Default, play: async (context) => { diff --git a/libs/components/src/utils/position-fixed-wrapper-decorator.ts b/libs/components/src/stories/storybook-decorators.ts similarity index 50% rename from libs/components/src/utils/position-fixed-wrapper-decorator.ts rename to libs/components/src/stories/storybook-decorators.ts index a3298e6ad03..d59f2dd1f3e 100644 --- a/libs/components/src/utils/position-fixed-wrapper-decorator.ts +++ b/libs/components/src/stories/storybook-decorators.ts @@ -11,7 +11,21 @@ export const positionFixedWrapperDecorator = (wrapper?: (story: string) => strin * https://github.com/storybookjs/storybook/issues/8011#issue-490251969 */ (story) => - /* HTML */ `
    + /* HTML */ `
    ${wrapper ? wrapper(story) : story}
    `, ); + +export const disableBothThemeDecorator = componentWrapperDecorator( + (story) => story, + ({ globals }) => { + /** + * avoid a bug with the way that we render the same component twice in the same iframe and how + * that interacts with the router-outlet + */ + const themeOverride = globals["theme"] === "both" ? "light" : globals["theme"]; + return { theme: themeOverride }; + }, +); From 65b393e3eeee84daa8c59b173476192f22eb64f0 Mon Sep 17 00:00:00 2001 From: Andy Pixley <3723676+pixman20@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:17:29 -0500 Subject: [PATCH 229/270] [BRE-563] Adding ability to skip electron publish (#12920) --- .github/workflows/publish-desktop.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 69ccd841065..a8719f71a66 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -7,22 +7,26 @@ on: publish_type: description: 'Publish Options' required: true - default: 'Initial Publish' + default: 'Publish' type: choice options: - - Initial Publish - - Republish + - Publish - Dry Run version: description: 'Version to publish (default: latest desktop release)' required: true type: string default: latest - rollout_percentage: - description: 'Staged Rollout Percentage' + electron_rollout_percentage: + description: 'Staged Rollout Percentage for Electron' required: true default: '10' type: string + electron_publish: + description: 'Publish Electron blob to AWS' + required: true + default: true + type: boolean snap_publish: description: 'Publish to Snap store' required: true @@ -107,6 +111,7 @@ jobs: name: Electron blob publish runs-on: ubuntu-22.04 needs: setup + if: inputs.electron_publish env: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} @@ -137,7 +142,7 @@ jobs: - name: Set staged rollout percentage env: RELEASE_CHANNEL: ${{ needs.setup.outputs.release_channel }} - ROLLOUT_PCT: ${{ inputs.rollout_percentage }} + ROLLOUT_PCT: ${{ inputs.electron_rollout_percentage }} run: | echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml From 2579c29c730c82fc5ecf6595995dfb72d0eded10 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:37:46 -0800 Subject: [PATCH 230/270] update button copy for generate passphrase. remove legacy css class (#12841) --- ...credential-generator-dialog.component.html | 21 +++++++++---------- .../generator/core/src/data/generators.ts | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html index d16f8e4f1cb..713118aeace 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html @@ -21,16 +21,15 @@ - + diff --git a/libs/tools/generator/core/src/data/generators.ts b/libs/tools/generator/core/src/data/generators.ts index 92b20b02b75..12209b402e0 100644 --- a/libs/tools/generator/core/src/data/generators.ts +++ b/libs/tools/generator/core/src/data/generators.ts @@ -58,7 +58,7 @@ const PASSPHRASE: CredentialGeneratorConfiguration< generateKey: "generatePassphrase", generatedValueKey: "passphrase", copyKey: "copyPassphrase", - useGeneratedValueKey: "useThisPassphrase", + useGeneratedValueKey: "useThisPassword", onlyOnRequest: false, request: [], engine: { From 795ad78a4e5aaff788841519fd8004a206626f3e Mon Sep 17 00:00:00 2001 From: Github Actions Date: Thu, 16 Jan 2025 22:01:33 +0000 Subject: [PATCH 231/270] Bumped Desktop client to 2025.1.4 --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index eaa27c3eb9f..249145eb3ea 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.3", + "version": "2025.1.4", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index e825bd41581..f3dc98b8d9b 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.3", + "version": "2025.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.3", + "version": "2025.1.4", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 6feed970798..7497d31d621 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.3", + "version": "2025.1.4", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 12a1468e3b7..28624e50a9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -232,7 +232,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.1.3", + "version": "2025.1.4", "hasInstallScript": true, "license": "GPL-3.0" }, From 374ea6af7c86ed99ba1e9ebc6a10a761dd1e32d9 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Thu, 16 Jan 2025 17:39:05 -0600 Subject: [PATCH 232/270] [PM-16719] [COMMUNITY] Debounce requestIdleCallback a single time every 100ms, as opposed to call requestIdleCallback on debounce method (#12695) * [COMMUNITY] Debounce requestIdleCallback a single time every 100ms, as opposed to call requestIdleCallback on debounce method Potential fix for #12031 * [COMMUNITY] Fixing broken jest mock of the debounce utils method * [COMMUNITY] Fixing broken jest mock of the debounce utils method * [COMMUNITY] Fixing broken jest mock of the debounce utils method --- .../services/collect-autofill-content.service.spec.ts | 2 +- .../autofill/services/collect-autofill-content.service.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts index 7471c298917..f15c3e4c389 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts @@ -21,7 +21,7 @@ jest.mock("../utils", () => { const utils = jest.requireActual("../utils"); return { ...utils, - debounce: jest.fn((fn) => fn), + debounce: jest.fn((fn, wait) => setTimeout(() => fn(), wait)), }; }); diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.ts index 13c546bccdb..8ccf726aa69 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -947,7 +947,8 @@ export class CollectAutofillContentService implements CollectAutofillContentServ } if (!this.mutationsQueue.length) { - requestIdleCallbackPolyfill(debounce(this.processMutations, 100), { timeout: 500 }); + // Collect all mutations and debounce the processing of those mutations by 100ms to ensure we don't process too many mutations at once. + debounce(this.processMutations, 100); } this.mutationsQueue.push(mutations); }; @@ -982,7 +983,8 @@ export class CollectAutofillContentService implements CollectAutofillContentServ const queueLength = this.mutationsQueue.length; if (!this.domQueryService.pageContainsShadowDomElements()) { - this.checkPageContainsShadowDom(); + // Checking if a page contains shadowDOM elements is a heavy operation and doesn't have to be done immediately, so we can call this within an idle moment on the event loop. + requestIdleCallbackPolyfill(this.checkPageContainsShadowDom, { timeout: 500 }); } for (let queueIndex = 0; queueIndex < queueLength; queueIndex++) { From 53f9d665697adf51f4cc8584a43f66a303c6b921 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:59:33 +0100 Subject: [PATCH 233/270] Autosync the updated translations (#12922) 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 | 11 ++++++++++- 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 | 9 +++++++++ apps/desktop/src/locales/ka/messages.json | 9 +++++++++ apps/desktop/src/locales/km/messages.json | 9 +++++++++ apps/desktop/src/locales/kn/messages.json | 9 +++++++++ apps/desktop/src/locales/ko/messages.json | 9 +++++++++ apps/desktop/src/locales/lt/messages.json | 9 +++++++++ apps/desktop/src/locales/lv/messages.json | 13 +++++++++++-- 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 | 13 +++++++++++-- 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, 572 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 5ee3ba7e029..1710005ab66 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 132021f7760..c2ccab5b908 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "تغيير البريد الإلكتروني الخاص بالحساب" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 9af089d1ef9..68753f204ea 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Hesabın e-poçtunu dəyişdir" + }, + "organizationUpgradeRequired": { + "message": "Təşkilat yüksəltmə tələb olunur" + }, + "upgradeOrganization": { + "message": "Təşkilatı yüksəlt" + }, + "upgradeOrganizationDesc": { + "message": "Bu özəllik, ödənişsiz təşkilatlar üçün əlçatan deyil. Daha çox özəlliyin kilidini açmaq üçün ödənişli plana keçin." } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e778c59525f..70b30128f9b 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 9b000177bbe..b3052fbc6cf 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Промяна на е-пощата" + }, + "organizationUpgradeRequired": { + "message": "Необходимо е надграждане на организацията" + }, + "upgradeOrganization": { + "message": "Надграждане на организацията" + }, + "upgradeOrganizationDesc": { + "message": "Безплатните планове нямат достъп до тази функционалност. Преминете към платен план, за да се възползвате от тази и много други възможности." } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 069c58d751d..7e1994cdeab 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index f5052559de9..487d98a327f 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 9d82ad59fad..46f876e560a 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 83f2840b724..28c20ee5184 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Změnit e-mail účtu" + }, + "organizationUpgradeRequired": { + "message": "Je vyžadována aktualizace organizace" + }, + "upgradeOrganization": { + "message": "Aktualizovat organizaci" + }, + "upgradeOrganizationDesc": { + "message": "Tato funkce není dostupná pro bezplatné organizace. Přepněte na placenou verzi a odemkněte další funkce." } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index f4e0853a933..897b5bb5508 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index ebe74818f47..0d1c94527b2 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Skift kontoe-mailadresse" + }, + "organizationUpgradeRequired": { + "message": "Organisationsopgradering krævet" + }, + "upgradeOrganization": { + "message": "Opgradér organisation" + }, + "upgradeOrganizationDesc": { + "message": "Denne funktion er utilgængelig for gratis organisationer. Skift til en betalingsabonnementstype for at oplåse flere funktioner." } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 133d98c3faa..5ce63034cb9 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -260,7 +260,7 @@ "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "um zusätzlichen Datenverlust zu vermeiden.", + "message": ", um zusätzlichen Datenverlust zu vermeiden.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "E-Mail-Adresse des Kontos ändern" + }, + "organizationUpgradeRequired": { + "message": "Organisations-Upgrade erforderlich" + }, + "upgradeOrganization": { + "message": "Organisation upgraden" + }, + "upgradeOrganizationDesc": { + "message": "Diese Funktion ist für kostenlose Organisationen nicht verfügbar. Wechsle zu einem kostenpflichtigen Abonnement, um weitere Funktionen freizuschalten." } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 145f386c6b9..3fed18252fe 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index acca06b8b4f..b33f2644403 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organisation upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organisation" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organisations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 122217dae9d..e3780c16984 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organisation upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organisation" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organisations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index a4621439f50..4ba31aa743b 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 1915002b6bd..00e17cadc40 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 8396316416b..a34820dc5a8 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 2daed855e52..ff7faec50b1 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index b79dc2d90ee..710e7ec3460 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 2d0a8cae996..fd3d9a8a3df 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Muuta tilin sähköpostiosoitetta" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 1f77b85e3c5..179cc7a2a55 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index f0ab36ba636..5e6c9ed71a0 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Changer le courriel du compte" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 1a02e5db4e7..bca12f16a7d 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index df61cefe73d..853e950dc0e 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 723ea0f6992..59c0f3df59f 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 109a1abff21..5fd7fae86a8 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Promjeni e-poštu računa" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 3716e1e67a9..19fd8a7fe49 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Fiók email cím megváltoztatása" + }, + "organizationUpgradeRequired": { + "message": "A szervezet felminősítése szükséges." + }, + "upgradeOrganization": { + "message": "Szervezeti áttérés" + }, + "upgradeOrganizationDesc": { + "message": "Ez a szolgáltatás nem elérhető ingyenes szervezeteknek. Váltás fizetős díjcsomagra a további funkciók feloldásához." } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index d4bcded0ee8..328200858e9 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index c85127fce20..e58d2fd665d 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Cambia l'e-mail dell'account" + }, + "organizationUpgradeRequired": { + "message": "Aggiornamento dell'organizzazione richiesto" + }, + "upgradeOrganization": { + "message": "Aggiorna organizzazione" + }, + "upgradeOrganizationDesc": { + "message": "Questa funzione non è disponibile per le organizzazioni gratuite. Passa a un piano a pagamento per sbloccare più funzioni." } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 9ce8f6a3ea7..671ab6c830e 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "アカウントのメールアドレスを変更する" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index bf434a6c29f..a1951c542ad 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 1a02e5db4e7..bca12f16a7d 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index f3401477f4a..840b73558c6 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 1aafdc4bc6d..38b1a2b9a55 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 11c70dd4197..1f010d33300 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 68495a25137..292cff77fba 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -2880,10 +2880,10 @@ "message": "Noteikta vāja parole, un tā ir atrasta datu noplūdē. Jāizmanto spēcīga un neatkārtojama parole, lai aizsargātu savu kontu. Vai tiešām izmantot šo paroli?" }, "useThisPassword": { - "message": "Use this password" + "message": "Izmantot šo paroli" }, "useThisUsername": { - "message": "Use this username" + "message": "Izmantot šo lietotājvārdu" }, "checkForBreaches": { "message": "Meklēt šo paroli zināmās datu noplūdēs" @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Mainīt konta e-pasta adresi" + }, + "organizationUpgradeRequired": { + "message": "Nepieciešams apvienības uzlabojums" + }, + "upgradeOrganization": { + "message": "Uzlabot apvienību" + }, + "upgradeOrganizationDesc": { + "message": "Šī iespēja nav pieejama bezmaksas apvienībām. Maksas plāna izvēle sniedz plašākas iespējas." } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index ac9994c3134..a03a80d01cd 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 15d4de334eb..41e4225c2df 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 1a02e5db4e7..bca12f16a7d 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index ca399731495..2c16b832f33 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 0ebecf3be8f..f6fe08a8979 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 624547d2121..7be28d2ced5 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 6f3670afa3d..877acb0d0dc 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "E-mailadres van het account veranderen" + }, + "organizationUpgradeRequired": { + "message": "Organisatie-upgrade vereist" + }, + "upgradeOrganization": { + "message": "Organisatie upgraden" + }, + "upgradeOrganizationDesc": { + "message": "Deze mogelijkheid is niet beschikbaar voor gratis organisaties. Schakel over naar een betaald abonnement om meer mogelijkheden te ontgrendelen." } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index df2f70ae839..ef5a0603761 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index ea55a1c6029..374cbf1bc95 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index d7b950100f5..9cc63555545 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 312fe4e8e89..33bceb61667 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Alterar e-mail" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index d88862a7980..61aa40e3307 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -2801,7 +2801,7 @@ "message": "Recusar início de sessão" }, "logInConfirmedForEmailOnDevice": { - "message": "Início de sessão confirmado para $EMAIL$ em $DEVICE$", + "message": "Início de sessão confirmado para $EMAIL$ no $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -2814,7 +2814,7 @@ } }, "youDeniedALogInAttemptFromAnotherDevice": { - "message": "Negou uma tentativa de início de sessão de outro dispositivo. Se foi realmente o caso, tente iniciar sessão com o dispositivo novamente." + "message": "Recusou uma tentativa de início de sessão de outro dispositivo. Se foi realmente o caso, tente iniciar sessão com o dispositivo novamente." }, "justNow": { "message": "Agora mesmo" @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Alterar o e-mail da conta" + }, + "organizationUpgradeRequired": { + "message": "Atualização da organização necessária" + }, + "upgradeOrganization": { + "message": "Atualizar organização" + }, + "upgradeOrganizationDesc": { + "message": "Esta funcionalidade não está disponível para organizações gratuitas. Mude para um plano pago para desbloquear mais funcionalidades." } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index d4ca5e21a5e..17b9d050f03 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 46e04b77704..408bc373178 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Изменить email аккаунта" + }, + "organizationUpgradeRequired": { + "message": "Требуется обновление организации" + }, + "upgradeOrganization": { + "message": "Обновить организацию" + }, + "upgradeOrganizationDesc": { + "message": "Эта функция недоступна для бесплатных организаций. Переключитесь на платный план, чтобы разблокировать дополнительные возможности." } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 5372bf59637..caec95368e5 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 07851ddc47e..1c253151ab1 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Zmeniť e-mail účtu" + }, + "organizationUpgradeRequired": { + "message": "Vyžaduje sa upgrade organizácie" + }, + "upgradeOrganization": { + "message": "Upgrade organizácie" + }, + "upgradeOrganizationDesc": { + "message": "Táto funkcia nie je k dispozícii pre bezplatné organizácie. Ak chcete odomknúť ďalšie funkcie, prejdite na platený plán." } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index c4fae11c815..cd36695335b 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 03cc5b8c265..d4109a70bf5 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 920981908fa..e26e44c6923 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 1a02e5db4e7..bca12f16a7d 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 7f7e373132c..3298ed16682 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 2fbd751ddb8..76ba6cb9d2d 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Hesap e-postasını değiştir" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index db0259adc05..4d9e8512421 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Змінити адресу е-пошти" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 07dcdfd373c..c0e79109ad8 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index c9046b34a55..54b51259a8b 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "更改账户电子邮箱" + }, + "organizationUpgradeRequired": { + "message": "需要升级组织" + }, + "upgradeOrganization": { + "message": "升级组织" + }, + "upgradeOrganizationDesc": { + "message": "此功能不适用于免费组织。请切换到付费计划以解锁更多功能。" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index a9206a50df6..c59d3f8ddfb 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -3474,5 +3474,14 @@ }, "changeAcctEmail": { "message": "更改帳號電子郵件位址" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } From 9eb7daa77d3b90c47f97ac2d5a7e3d938c124cc1 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:13:00 +0100 Subject: [PATCH 234/270] Autosync the updated translations (#12923) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 17 +++--- apps/browser/src/_locales/az/messages.json | 17 +++--- apps/browser/src/_locales/be/messages.json | 17 +++--- apps/browser/src/_locales/bg/messages.json | 19 +++---- apps/browser/src/_locales/bn/messages.json | 17 +++--- apps/browser/src/_locales/bs/messages.json | 17 +++--- apps/browser/src/_locales/ca/messages.json | 17 +++--- apps/browser/src/_locales/cs/messages.json | 17 +++--- apps/browser/src/_locales/cy/messages.json | 17 +++--- apps/browser/src/_locales/da/messages.json | 17 +++--- apps/browser/src/_locales/de/messages.json | 25 ++++----- apps/browser/src/_locales/el/messages.json | 19 +++---- apps/browser/src/_locales/en_GB/messages.json | 19 +++---- apps/browser/src/_locales/en_IN/messages.json | 19 +++---- apps/browser/src/_locales/es/messages.json | 19 +++---- apps/browser/src/_locales/et/messages.json | 17 +++--- apps/browser/src/_locales/eu/messages.json | 17 +++--- apps/browser/src/_locales/fa/messages.json | 17 +++--- apps/browser/src/_locales/fi/messages.json | 19 +++---- apps/browser/src/_locales/fil/messages.json | 17 +++--- apps/browser/src/_locales/fr/messages.json | 19 +++---- apps/browser/src/_locales/gl/messages.json | 47 ++++++++-------- apps/browser/src/_locales/he/messages.json | 19 +++---- apps/browser/src/_locales/hi/messages.json | 17 +++--- apps/browser/src/_locales/hr/messages.json | 19 +++---- apps/browser/src/_locales/hu/messages.json | 17 +++--- apps/browser/src/_locales/id/messages.json | 19 +++---- apps/browser/src/_locales/it/messages.json | 19 +++---- apps/browser/src/_locales/ja/messages.json | 19 +++---- apps/browser/src/_locales/ka/messages.json | 17 +++--- apps/browser/src/_locales/km/messages.json | 17 +++--- apps/browser/src/_locales/kn/messages.json | 17 +++--- apps/browser/src/_locales/ko/messages.json | 19 +++---- apps/browser/src/_locales/lt/messages.json | 17 +++--- apps/browser/src/_locales/lv/messages.json | 19 +++---- apps/browser/src/_locales/ml/messages.json | 17 +++--- apps/browser/src/_locales/mr/messages.json | 17 +++--- apps/browser/src/_locales/my/messages.json | 17 +++--- apps/browser/src/_locales/nb/messages.json | 19 +++---- apps/browser/src/_locales/ne/messages.json | 17 +++--- apps/browser/src/_locales/nl/messages.json | 17 +++--- apps/browser/src/_locales/nn/messages.json | 17 +++--- apps/browser/src/_locales/or/messages.json | 17 +++--- apps/browser/src/_locales/pl/messages.json | 19 +++---- apps/browser/src/_locales/pt_BR/messages.json | 19 +++---- apps/browser/src/_locales/pt_PT/messages.json | 17 +++--- apps/browser/src/_locales/ro/messages.json | 17 +++--- apps/browser/src/_locales/ru/messages.json | 17 +++--- apps/browser/src/_locales/si/messages.json | 17 +++--- apps/browser/src/_locales/sk/messages.json | 17 +++--- apps/browser/src/_locales/sl/messages.json | 17 +++--- apps/browser/src/_locales/sr/messages.json | 19 +++---- apps/browser/src/_locales/sv/messages.json | 17 +++--- apps/browser/src/_locales/te/messages.json | 17 +++--- apps/browser/src/_locales/th/messages.json | 17 +++--- apps/browser/src/_locales/tr/messages.json | 43 +++++++-------- apps/browser/src/_locales/uk/messages.json | 39 +++++++------- apps/browser/src/_locales/vi/messages.json | 19 +++---- apps/browser/src/_locales/zh_CN/messages.json | 17 +++--- apps/browser/src/_locales/zh_TW/messages.json | 19 +++---- apps/browser/store/locales/ru/copy.resx | 53 +++++++++---------- 61 files changed, 509 insertions(+), 690 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 8b4bbe23e04..30f20dfff1d 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "الموقع $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 86f1d07fb3f..9184b84d735 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Bu veb saytlar üçün avto-doldurma və digər əlaqəli özəlliklər təklif olunmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." }, - "autofillBlockedNotice": { - "message": "Bu veb sayt üçün avto-doldurma əngəllənib. Bunu ayarlarda incələyin və ya dəyişdirin." + "autofillBlockedNoticeV2": { + "message": "Bu veb sayt üçün avto-doldurma əngəllənib." }, - "autofillBlockedTooltip": { - "message": "Bu veb saytda avto-doldurma əngəllənib. Ayarlarda incələyin." + "autofillBlockedNoticeGuidance": { + "message": "Bunu ayarlarda dəyişdir" }, "websiteItemLabel": { "message": "Veb sayt $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Avto-doldurma təklifləri" }, + "itemSuggestions": { + "message": "Təklif olunan elementlər" + }, "autofillSuggestionsTip": { "message": "Bu saytın avto-doldurması üçün giriş elementini saxlayın" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Mətn \"Send\"ləri" }, - "bitwardenNewLook": { - "message": "Bitwarden-in yeni bir görünüşü var!" - }, - "bitwardenNewLookDesc": { - "message": "Seyf vərəqindən avto-doldurma və axtarış etmə artıq daha asan və intuitivdir. Nəzər salın!" - }, "accountActions": { "message": "Hesab fəaliyyətləri" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 036b1dfcb24..66545d5666b 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Вэб-сайт $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 098e2b91051..d4f81883672 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Автоматичното попълване и други свързани функции няма да бъдат предлагани за тези уеб сайтове. Трябва да презаредите страницата, за да влязат в сила промените." }, - "autofillBlockedNotice": { - "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате и промените това в настройките." + "autofillBlockedNoticeV2": { + "message": "Автоматичното попълване е блокирано за този уеб сайт." }, - "autofillBlockedTooltip": { - "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате това в настройките." + "autofillBlockedNoticeGuidance": { + "message": "Променете това в настройките" }, "websiteItemLabel": { "message": "Уеб сайт $number$ (адрес)", @@ -4008,7 +4008,10 @@ "message": "Секретният ключ е премахнат" }, "autofillSuggestions": { - "message": "Автоматично попълване на предложения" + "message": "Предложения за авт. попълване" + }, + "itemSuggestions": { + "message": "Препоръчани елементи" }, "autofillSuggestionsTip": { "message": "Запазване на елемент за вписване за този уеб сайт, за авт. попълване" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Текстови изпращания" }, - "bitwardenNewLook": { - "message": "Биуорден има нов облик!" - }, - "bitwardenNewLookDesc": { - "message": "Сега е по-лесно и интуитивно от всякога да използвате автоматичното попълване и да търсите в раздела на трезора. Разгледайте!" - }, "accountActions": { "message": "Действия по регистрацията" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index f60fc2c9683..8180813a2cf 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index aaf04da98b7..0bd4144f6cd 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index e7113d5aab1..ec7682f5013 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index eb2f9149daf..c40ddfc6ed9 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Automatické vyplňování a další související funkce nebudou pro tyto webové stránky nabízeny. Aby se změny projevily, musíte stránku aktualizovat." }, - "autofillBlockedNotice": { - "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to nebo to změňte v nastavení." + "autofillBlockedNoticeV2": { + "message": "Automatické vyplňování je pro tuto stránku zablokováno." }, - "autofillBlockedTooltip": { - "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to v nastavení." + "autofillBlockedNoticeGuidance": { + "message": "Změňte to v nastavení" }, "websiteItemLabel": { "message": "Webová stránka $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Návrhy automatického vyplňování" }, + "itemSuggestions": { + "message": "Navrhované položky" + }, "autofillSuggestionsTip": { "message": "Uložit přihlašovací údaje pro tuto stránku do automatického vyplňování" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Sends s texty" }, - "bitwardenNewLook": { - "message": "Bitwarden má nový vzhled!" - }, - "bitwardenNewLookDesc": { - "message": "Je snazší a intuitivnější než kdy jindy automaticky vyplňovat a vyhledávat z karty trezor. Mrkněte se!" - }, "accountActions": { "message": "Činnosti účtu" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 8ad494f0bfb..cfa74caaa32 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 9008049e1a4..c5e69fc375b 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofyldning og andre relaterede funktioner tilbydes ikke på disse websteder. Siden skal opdateres for at effektuere ændringerne." }, - "autofillBlockedNotice": { - "message": "Autoudfyldning er blokeret på dette websted. Gennemgå eller ændr dette i Indstillinger." + "autofillBlockedNoticeV2": { + "message": "Autoudfyldning blokeret for dette websted." }, - "autofillBlockedTooltip": { - "message": "Autoudfyldning er blokeret på dette websted. Gennemgå i Indstillinger." + "autofillBlockedNoticeGuidance": { + "message": "Ændr dette i Indstillinger" }, "websiteItemLabel": { "message": "Websted $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autoudfyldningsforslag" }, + "itemSuggestions": { + "message": "Foreslåede emner" + }, "autofillSuggestionsTip": { "message": "Gem et loginemne for dette websted til autoudfyldning" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Tekst-Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden har fået et nyt look!" - }, - "bitwardenNewLookDesc": { - "message": "Det er lettere og mere intuitivt end nogensinde at autoudfylde og søge via fanen Boks. Tag et kig omkring!" - }, "accountActions": { "message": "Kontohandlinger" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index fce84d1b431..872307dd945 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2337,13 +2337,13 @@ "message": "Bitwarden wird für alle angemeldeten Konten nicht danach fragen Zugangsdaten für diese Domains speichern. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, "blockedDomainsDesc": { - "message": "Automatisches Ausfüllen und andere zugehörige Funktionen werden für diese Webseiten nicht angeboten. Sie müssen die Seite aktualisieren, damit die Änderungen wirksam werden." + "message": "Automatisches Ausfüllen und andere zugehörige Funktionen werden für diese Webseiten nicht angeboten. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, - "autofillBlockedNotice": { - "message": "Das automatische Ausfüllen ist für diese Website gesperrt. Dieses Verhalten kann in den Einstellungen überprüft oder geändert werden." + "autofillBlockedNoticeV2": { + "message": "Automatisches Ausfüllen ist für diese Website gesperrt." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Dies in den Einstellungen ändern" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -2808,14 +2808,14 @@ "message": "Entschlüsselungsfehler" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden konnte den unten aufgelisteten Tresoreintrag bzw. die Tresoreinträge nicht entschlüsseln." + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Kontaktiere den Kundensupport", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "um zusätzlichen Datenverlust zu vermeiden.", + "message": ", um zusätzlichen Datenverlust zu vermeiden.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Vorschläge zum Auto-Ausfüllen" }, + "itemSuggestions": { + "message": "Vorgeschlagene Einträge" + }, "autofillSuggestionsTip": { "message": "Speichere einen Login-Eintrag für diese Seite zum automatischen Ausfüllen" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text-Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden hat einen neuen Look!" - }, - "bitwardenNewLookDesc": { - "message": "Auto-Ausfüllen und Suchen vom Tresor-Tab ist einfacher und intuitiver als je zuvor. Schau dich um!" - }, "accountActions": { "message": "Konto-Aktionen" }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index bdb0eb2c17d..37ddbc081f7 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Η αυτόματη συμπλήρωση και άλλες σχετικές λειτουργίες δεν θα προσφερθούν για αυτούς τους ιστότοπους. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Ιστοσελίδα $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Το κλειδί πρόσβασης αφαιρέθηκε" }, "autofillSuggestions": { - "message": "Προτάσεις αυτόματης συμπλήρωσης" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Αποθηκεύστε ένα αντικείμενο σύνδεσης για την αυτόματη συμπλήρωση αυτού του ιστοτόπου" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Send κειμένων" }, - "bitwardenNewLook": { - "message": "Το Bitwarden έχει μια νέα εμφάνιση!" - }, - "bitwardenNewLookDesc": { - "message": "Είναι πιο ευκολότερο και πιο διαισθητικό από ποτέ στην αυτόματη συμπλήρωση και αναζήτηση από την καρτέλα Θησαυ/κιο. Ρίξτε μια ματιά τριγύρω!" - }, "accountActions": { "message": "Ενέργειες λογαριασμού" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 7e938bb6b2c..5a3ca3893b7 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to auto-fill" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 70e725c3a88..0783f0e9172 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to auto-fill" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index d9cd2517816..37a823b77f4 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Clave de acceso eliminada" }, "autofillSuggestions": { - "message": "Autocompletar sugerencias" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Guarda un elemento de inicio de sesión para este sitio para autocompletar" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden tiene un aspecto nuevo." - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Acciones de cuenta" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 5245de4fb7e..b4401bd8850 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index fc875be6da0..67d45a17458 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index d2362a2655f..1eff69e292f 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 0dd96ef9a02..143cc603144 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Verkkotunnus $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Pääsyavain poistettiin" }, "autofillSuggestions": { - "message": "Automaattitäytön ehdotukset" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Tallenna tälle sivustolle automaattisesti täytettävä kirjautumistieto." @@ -4586,12 +4589,6 @@ "textSends": { "message": "Teksti-Sendit" }, - "bitwardenNewLook": { - "message": "Bitwardenilla on uusi ulkoasu!" - }, - "bitwardenNewLookDesc": { - "message": "Automaattinen täyttö ja sisällön haku Holvi-välilehdeltä on nyt entistä helpompaa ja luontevampaa. Kokeile nyt!" - }, "accountActions": { "message": "Tilitoiminnot" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index d0fdf1018fb..0c24db214a2 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 913391d218c..890d00b644f 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Site web $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Clé d'identification (passkey) retirée" }, "autofillSuggestions": { - "message": "Suggestions de saisie automatique" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Enregistrez un élément de connexion à remplir automatiquement pour ce site" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden a un nouveau look !" - }, - "bitwardenNewLookDesc": { - "message": "Il est plus facile et plus intuitif que jamais de remplir automatiquement les champs et d'effectuer des recherches à partir de l'onglet \"Coffre\". Jetez un coup d'œil !" - }, "accountActions": { "message": "Actions du compte" }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index e655159f246..9f5e7771a14 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2325,7 +2325,7 @@ "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Blocked domains" + "message": "Dominios bloqueados" }, "excludedDomains": { "message": "Dominios excluídos" @@ -2337,13 +2337,13 @@ "message": "Bitwarden non ofrecerá gardar contas para estes dominios en ningunha das sesións iniciadas. Recarga a páxina para que os cambios fornezan efecto." }, "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": "O autoenchido e outras funcións relacionadas non estarán dispoñibles para estas webs. Debes recargar a páxina para que os cambios teñan efecto." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "O autoenchido está bloqueado para esta web." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Cambia isto en axustes" }, "websiteItemLabel": { "message": "Web $number$ (URI)", @@ -2364,7 +2364,7 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Blocked domain changes saved" + "message": "Dominios bloqueados gardados" }, "excludedDomainsSavedSuccess": { "message": "Dominios excluídos gardados" @@ -2805,17 +2805,17 @@ "message": "Erro" }, "decryptionError": { - "message": "Decryption error" + "message": "Erro de descifrado" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden non puido descifrar os seguintes elementos." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Contacto co cliente exitoso", "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": "para evitar a perda de datos.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { @@ -4008,7 +4008,10 @@ "message": "Clave de acceso eliminada" }, "autofillSuggestions": { - "message": "Suxestións de autoenchido" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Entradas suxeridas" }, "autofillSuggestionsTip": { "message": "Gardar unha credencial como suxestión para este sitio" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Textos Send" }, - "bitwardenNewLook": { - "message": "Bitwarden ten un novo look!" - }, - "bitwardenNewLookDesc": { - "message": "É máis fácil e intuitivo que nunca autoencher e buscar dende a caixa forte. Bota un ollo!" - }, "accountActions": { "message": "Accións da conta" }, @@ -4671,22 +4668,22 @@ "message": "Non tes permiso para modificar esta entrada" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "O desbloqueo biométrico non está dispoñible porque se require o PIN ou contrasinal primeiro." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "O desbloqueo biométrico non está dispoñible." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "O desbloqueo biométrico non está dispoñible por arquivos do sistema desconfigurados." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "O desbloqueo biométrico non está dispoñible por arquivos do sistema desconfigurados." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "O desbloqueo biométrico non está dispoñible porque a aplicación de escritorio está pechada." }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "O desbloqueo biométrico non está dispoñible porque non está activada para $EMAIL$ na aplicación de escritorio.", "placeholders": { "email": { "content": "$1", @@ -4695,7 +4692,7 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "O desbloqueo biométrico non está dispoñible por algunha razón non prevista." }, "authenticating": { "message": "Autenticando" diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 1a3057ac291..19c0d292d14 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "הצעות למילוי אוטומטי" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index fd4a6612af4..3db3ef3e293 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "बिटवार्डन का नया रूप!" - }, - "bitwardenNewLookDesc": { - "message": "वॉल्ट टैब से ऑटोफिल और सर्च करना पहले से कहीं ज़्यादा आसान और सहज है। सबकुछ ध्यान से देखें!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 331bc109309..8d830fd6dc5 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Web stranica $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Pristupni ključ uklonjen" }, "autofillSuggestions": { - "message": "Prijedlozi auto-ispune" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Spremi u auto-ispunu stavku prijave za ovu stranicu" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Send tekstovi" }, - "bitwardenNewLook": { - "message": "Bitwarden ima novi izgled!" - }, - "bitwardenNewLookDesc": { - "message": "Auto-ispuna i pretraga iz kartice Trezor je lakša i intuitivnija nego ikad prije. Razgledaj!" - }, "accountActions": { "message": "Radnje na računu" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 0aea2c7eced..07bc92c342a 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Az automatikus kitöltés és az egyéb kapcsolódó funkciók ezeken a webhelyeken nincsenek a kínálatban. A változtatások életbe lépéséhez frissíteni kell az oldalt." }, - "autofillBlockedNotice": { - "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át vagy módosítsuk ezt a beállításokban." + "autofillBlockedNoticeV2": { + "message": "Az automatikus kitöltés blokkolásra került ezen a webhelyen." }, - "autofillBlockedTooltip": { - "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át ezt a beállításokban." + "autofillBlockedNoticeGuidance": { + "message": "Megváltoztatás a beállításokban" }, "websiteItemLabel": { "message": "Webhely $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Automatikus kitöltés javaslatok" }, + "itemSuggestions": { + "message": "Javasolt elemek" + }, "autofillSuggestionsTip": { "message": "A bejelentkezési elem mentése ehhez a webhelyhez az automatikus kitöltéshez" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Szöveg küldés" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Fiókműveletek" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 82776a8e82b..9755e7322a2 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Situs web $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Kunci sandi dihapus" }, "autofillSuggestions": { - "message": "Saran isi otomatis" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Simpan benda login untuk situs ini ke isi otomatis" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 04e2c4ee64f..e0a868717a5 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Per questi siti, l'auto-completamento e funzionalità simili non saranno disponibili. Ricarica la pagina per applicare le modifiche." }, - "autofillBlockedNotice": { - "message": "L'auto-completamento è bloccato per questo sito. Modifica questa scelta nelle impostazioni." + "autofillBlockedNoticeV2": { + "message": "La compilazione automatica è bloccata per questo sito." }, - "autofillBlockedTooltip": { - "message": "L'auto-completamento è bloccato per questo sito. Verifica nelle impostazioni." + "autofillBlockedNoticeGuidance": { + "message": "Modifica questo nelle impostazioni" }, "websiteItemLabel": { "message": "Sito $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Passkey rimossa" }, "autofillSuggestions": { - "message": "Suggerimenti per il riempimento automatico" + "message": "Suggerimenti riempimento automatico" + }, + "itemSuggestions": { + "message": "Elementi suggeriti" }, "autofillSuggestionsTip": { "message": "Salva un elemento di accesso per questo sito da riempire automaticamente" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Send Testo" }, - "bitwardenNewLook": { - "message": "Bitwarden ha un nuovo look!" - }, - "bitwardenNewLookDesc": { - "message": "È più facile e intuitivo che mai utilizzare il riempimento automatico e cercare dalla scheda Cassaforte. Dai un'occhiata!" - }, "accountActions": { "message": "Azioni dell'account" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index cc1f34e4985..81b81cf16c8 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "ウェブサイト $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "パスキーを削除しました" }, "autofillSuggestions": { - "message": "候補を自動入力する" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "自動入力するためにこのサイトのログインアイテムを保存します" @@ -4586,12 +4589,6 @@ "textSends": { "message": "テキスト Send" }, - "bitwardenNewLook": { - "message": "Bitwarden が新しい外観になりました。" - }, - "bitwardenNewLookDesc": { - "message": "保管庫タブからの自動入力と検索がこれまで以上に簡単で直感的になりました。" - }, "accountActions": { "message": "アカウントの操作" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 50fdc6613c5..9bd12ce6017 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index e34751eea7d..ad7d2582146 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 3f9e99e5637..d613ba26953 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 4ac6d281b09..8aad2f586c7 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "웹사이트 $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "패스키 제거됨" }, "autofillSuggestions": { - "message": "자동 완성 제안" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "이 사이트에서 자동으로 작성할 로그인 항목 저장" @@ -4586,12 +4589,6 @@ "textSends": { "message": "텍스트 Send" }, - "bitwardenNewLook": { - "message": "Bitwarden이 새로운 모습으로 돌아왔습니다!" - }, - "bitwardenNewLookDesc": { - "message": "보관함 탭에서 자동 완성하고 검색하는 것이 그 어느 때보다 쉽고 직관적입니다. 둘러보세요!" - }, "accountActions": { "message": "계정 작업" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 3c81df00f10..a29c60d5e8c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index fc682ced389..c1cdd0182a9 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Automātiskā aizpilde un citas saistītās iespējas šajās tīmekļvietnēs netiks piedāvātas. Ir jāatsvaidzina lapa, lai izmaiņas iedarbotos." }, - "autofillBlockedNotice": { - "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo pārskatīt vai mainīt var iestatījumos." + "autofillBlockedNoticeV2": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta." }, - "autofillBlockedTooltip": { - "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo var pārskatīt iestatījumos." + "autofillBlockedNoticeGuidance": { + "message": "To var mainīt iestatījumos" }, "websiteItemLabel": { "message": "Tīmekļvietne $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Piekļuves atslēga noņemta" }, "autofillSuggestions": { - "message": "Ieteikumi automātiskajai aizpildei" + "message": "Automātiskās aizpildes ieteikumi" + }, + "itemSuggestions": { + "message": "Ieteiktie vienumi" }, "autofillSuggestionsTip": { "message": "Saglabāt pieteikšanās vienumi, ko automātiski aizpildīt šajā vietnē" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Teksta Send" }, - "bitwardenNewLook": { - "message": "Bitwarden ir jauns izskats." - }, - "bitwardenNewLookDesc": { - "message": "Veikt automātisko aizpildi un meklēšanu glabātavas cilnē ir vienkāršāk un izprotamāk kā jebkad. Apskati izmaiņas!" - }, "accountActions": { "message": "Konta darbības" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 4cbbfc46d6a..f985f6377ac 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index cbb0b1bdf1a..26c0af364ad 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index e34751eea7d..ad7d2582146 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 3a12c9ae4f4..17cd112b2e8 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Autoutfyllingsforslag" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden har fått et nytt utseende!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index e34751eea7d..ad7d2582146 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 0112ded1983..c8e0b46f7e4 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill en andere gerelateerde functies worden niet aangeboden voor deze websites. Vernieuw de pagina om de wijzigingen toe te passen." }, - "autofillBlockedNotice": { - "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk of verander dit in de instellingen." + "autofillBlockedNoticeV2": { + "message": "Autofill is geblokkeerd voor deze website." }, - "autofillBlockedTooltip": { - "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk in de instellingen." + "autofillBlockedNoticeGuidance": { + "message": "Dit aanpassen in instellingen" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Suggesties automatisch invullen" }, + "itemSuggestions": { + "message": "Voorgestelde items" + }, "autofillSuggestionsTip": { "message": "Inlogitem opslaan voor automatisch invullen op deze site" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Tekst-Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden heeft een nieuw uiterlijk!" - }, - "bitwardenNewLookDesc": { - "message": "Automatisch invullen en zoeken is makkelijker en intuïtiever dan ooit vanaf het tabblad Kluis. Kijk rond!" - }, "accountActions": { "message": "Accountacties" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index e34751eea7d..ad7d2582146 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index e34751eea7d..ad7d2582146 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 4b7d3a19fc4..b14cd9961a0 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Strona internetowa $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Passkey został usunięty" }, "autofillSuggestions": { - "message": "Sugestie autouzupełnienia" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Zapisz element logowania dla tej witryny, aby automatycznie wypełnić" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden ma nowy wygląd!" - }, - "bitwardenNewLookDesc": { - "message": "Auto wypełnianie i szukanie na zakładce sejfu jest teraz prostsze i bardziej intuicyjne. Rozejrzyj się tam!" - }, "accountActions": { "message": "Akcje konta" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index e3409b1da52..3ac6412a3cd 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Site $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Chave de acesso removida" }, "autofillSuggestions": { - "message": "Sugestões de autopreenchimento" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Salvar um item de login para este site autopreenchimento" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Texto enviado" }, - "bitwardenNewLook": { - "message": "Bitwarden tem uma nova aparência!" - }, - "bitwardenNewLookDesc": { - "message": "É mais fácil e mais intuitivo do que nunca autopreenchimento e pesquise na guia Cofre. Dê uma olhada ao redor!" - }, "accountActions": { "message": "Ações da conta" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 6b3c190f0b5..f2f7cd23247 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "O preenchimento automático e outras funcionalidades relacionadas não serão disponibilizados para estes sites. É necessário atualizar a página para que as alterações tenham efeito." }, - "autofillBlockedNotice": { - "message": "O preenchimento automático está bloqueado para este site. Reveja ou altere esta opção nas definições." + "autofillBlockedNoticeV2": { + "message": "O preenchimento automático está bloqueado para este site." }, - "autofillBlockedTooltip": { - "message": "O preenchimento automático está bloqueado neste site. Reveja nas definições." + "autofillBlockedNoticeGuidance": { + "message": "Alterar esta opção nas definições" }, "websiteItemLabel": { "message": "Site $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Sugestões de preenchimento automático" }, + "itemSuggestions": { + "message": "Itens sugeridos" + }, "autofillSuggestionsTip": { "message": "Guarde uma credencial deste site para preenchimento automático" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Sends de texto" }, - "bitwardenNewLook": { - "message": "O Bitwarden tem um novo visual!" - }, - "bitwardenNewLookDesc": { - "message": "É mais fácil e mais intuitivo do que nunca preencher automaticamente e pesquisar a partir do separador Cofre. Dê uma vista de olhos!" - }, "accountActions": { "message": "Ações da conta" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 114c01aff44..04b3e02fc98 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 53f31813ac5..605f1bb9a49 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Автозаполнение и другие связанные с ним функции не будут предлагаться для этих сайтов. Чтобы изменения вступили в силу, необходимо обновить страницу." }, - "autofillBlockedNotice": { - "message": "Автозаполнение для этого сайта заблокировано. Просмотрите или измените это в настройках." + "autofillBlockedNoticeV2": { + "message": "Автозаполнение для этого сайта заблокировано." }, - "autofillBlockedTooltip": { - "message": "Автозаполнение на этом сайте заблокировано. Просмотрите в настройках." + "autofillBlockedNoticeGuidance": { + "message": "Измените это в настройках" }, "websiteItemLabel": { "message": "Сайт $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Предложения по автозаполнению" }, + "itemSuggestions": { + "message": "Предлагаемые элементы" + }, "autofillSuggestionsTip": { "message": "Сохранить логин для этого сайта для автозаполнения" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Текстовая Send" }, - "bitwardenNewLook": { - "message": "У Bitwarden новый облик!" - }, - "bitwardenNewLookDesc": { - "message": "Теперь автозаполнение и поиск на вкладке Хранилище стали проще и интуитивно понятнее, чем когда-либо. Осмотритесь!" - }, "accountActions": { "message": "Действия аккаунта" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 56cf378344f..6a7bb304315 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 08bfcc79f6a..e1020228e87 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Automatické vypĺňanie a ďalšie súvisiace funkcie sa na týchto webových stránkach nebudú ponúkať. Aby sa zmeny prejavili, musíte stránku obnoviť." }, - "autofillBlockedNotice": { - "message": "Automatické vypĺňanie je pre túto webovú stránku zablokované. Skontrolujte alebo zmeňte to v nastaveniach." + "autofillBlockedNoticeV2": { + "message": "Automatické vypĺňanie je pre túto webovú stránku zablokované." }, - "autofillBlockedTooltip": { - "message": "Automatické vypĺňanie je na tejto webovej stránke zablokované. Pozrite v nastaveniach." + "autofillBlockedNoticeGuidance": { + "message": "Zmeňte to v nastaveniach" }, "websiteItemLabel": { "message": "Webstránka $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Návrhy automatického vypĺňania" }, + "itemSuggestions": { + "message": "Navrhované položky" + }, "autofillSuggestionsTip": { "message": "Uložte položku prihlásenia pre tento web na automatické vyplnenie" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Textové Sendy" }, - "bitwardenNewLook": { - "message": "Bitwarden má nový vzhľad!" - }, - "bitwardenNewLookDesc": { - "message": "Automatické vypĺňanie a vyhľadávanie na karte Trezor je jednoduchšie a intuitívnejšie ako kedykoľvek predtým. Poobzerajte sa!" - }, "accountActions": { "message": "Operácie s účtom" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 2d2ee455415..db8558b0a32 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 01d95a6ed1b..948956bb360 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Сајт $number$ (УРЛ)", @@ -4008,7 +4008,10 @@ "message": "Приступни кључ је уклоњен" }, "autofillSuggestions": { - "message": "Предлози за ауто-попуњавање" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Сачувајте ставку за пријаву за ову локацију за ауто-попуњавање" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Текст „Send“" }, - "bitwardenNewLook": { - "message": "Bitwarden има нови изглед!" - }, - "bitwardenNewLookDesc": { - "message": "Лакше је и интуитивније него икада да се аутоматски попуњава и тражи са картице Сефа. Проверите!" - }, "accountActions": { "message": "Акције везане за налог" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index a443a8e6b2e..47e9dc2cff6 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Webbplats $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden har fått ett nytt utseende!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Kontoåtgärder" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index e34751eea7d..ad7d2582146 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 1b493de3d2c..b3cddf4e83a 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Autofill suggestions" }, + "itemSuggestions": { + "message": "Suggested items" + }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 8095a9f6045..1d60fbed0e9 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -1275,7 +1275,7 @@ "message": "Bitwarden'ı desteklediğiniz için teşekkür ederiz." }, "premiumFeatures": { - "message": "Premium'a yükseltin ve şunları alın:" + "message": "Premium'a geçmenin avantajları:" }, "premiumPrice": { "message": "Bunların hepsi sadece yılda $PRICE$!", @@ -1502,13 +1502,13 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "enableAutoFillOnPageLoad": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "enableAutoFillOnPageLoadDesc": { - "message": "Sayfa yüklendiğinde giriş formu tespit edilirse otomatik olarak formu doldur." + "message": "Sayfa yüklenince giriş formu tespit edilirse otomatik olarak formu doldur." }, "experimentalFeature": { "message": "Ele geçirilmiş veya güvenilmeyen web siteleri sayfa yüklenirken otomatik doldurmayı suistimal edebilir." @@ -1523,19 +1523,19 @@ "message": "Hesaplar için varsayılan otomatik doldurma ayarı" }, "defaultAutoFillOnPageLoadDesc": { - "message": "\"Sayfa yüklendiğinde otomatik doldur\"u her hesabın \"Düzenle\" görünümünden ayrı ayrı kapatabilirsiniz." + "message": "\"Sayfa yüklenince otomatik doldur\"u her hesabın \"Düzenle\" görünümünden ayrı ayrı kapatabilirsiniz." }, "itemAutoFillOnPageLoad": { - "message": "Sayfa yüklendiğinde otomatik doldur (Seçeneklerde ayarlanmışsa)" + "message": "Sayfa yüklenince otomatik doldur (Seçeneklerde ayarlanmışsa)" }, "autoFillOnPageLoadUseDefault": { "message": "Varsayılan ayarı kullan" }, "autoFillOnPageLoadYes": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "autoFillOnPageLoadNo": { - "message": "Sayfa yüklendiğinde otomatik doldurma" + "message": "Sayfa yüklenince otomatik doldurma" }, "commandOpenPopup": { "message": "Kasayı açılır pencerede aç" @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Bu sitede otomatik doldurma engellenmiş." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Bunu ayarlardan değiştirebilirsiniz" }, "websiteItemLabel": { "message": "Web sitesi $number$ (URI)", @@ -3151,7 +3151,7 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Kuruluş ilkeleriniz, sayfa yüklendiğinde otomatik doldurmayı etkinleştirdi." + "message": "Kuruluş ilkeleriniz, sayfa yüklenince otomatik doldurmayı etkinleştirdi." }, "howToAutofill": { "message": "Otomatik doldurma nasıl yapılır?" @@ -3461,11 +3461,11 @@ "message": "Alias alan adı" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Ana parolayı yeniden isteyen kayıtlar sayfa yüklendiğinde otomatik olarak doldurulamaz. Sayfa yüklendiğinde otomatik doldurma kapatıldı.", + "message": "Ana parolayı yeniden isteyen kayıtlar sayfa yüklenince otomatik olarak doldurulamaz. Sayfa yüklenince otomatik doldurma kapatıldı.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Sayfa yüklendiğinde otomatik doldurma, varsayılan ayarı kullanacak şekilde ayarlandı.", + "message": "Sayfa yüklenince otomatik doldurma, varsayılan ayarı kullanacak şekilde ayarlandı.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { @@ -3740,7 +3740,7 @@ "message": "Geçiş anahtarı" }, "accessing": { - "message": "Erişim" + "message": "Erişilen konum:" }, "loggedInExclamation": { "message": "Giriş yapıldı!" @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Önerileri otomatik doldur" }, + "itemSuggestions": { + "message": "Önerilen kayıtlar" + }, "autofillSuggestionsTip": { "message": "Otomatik doldurma için bu siteye ait bir hesap kaydededin" }, @@ -4333,7 +4336,7 @@ } }, "autoFillOnPageLoad": { - "message": "Sayfa yüklendiğinde otomatik doldur" + "message": "Sayfa yüklenince otomatik doldur" }, "cardExpiredTitle": { "message": "Kartın süresi dolmuş" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Metin Send'leri" }, - "bitwardenNewLook": { - "message": "Bitwarden'ın tasarımı güncellendi!" - }, - "bitwardenNewLookDesc": { - "message": "Otomatik doldurma ve kasanızda arama yapma artık eskisinden daha kolay. Yeni tasarıma göz atmayı unutmayın!" - }, "accountActions": { "message": "Hesap işlemleri" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index d6b0b88ead2..2d35489e09a 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Автозаповнення та інші пов'язані функції не пропонуватимуться для цих вебсайтів. Вам слід оновити сторінку для застосування змін." }, - "autofillBlockedNotice": { - "message": "Автозаповнення заблоковано для цього вебсайту. Перегляньте або змініть це в налаштуваннях." + "autofillBlockedNoticeV2": { + "message": "Автозаповнення для цього вебсайту заблоковано." }, - "autofillBlockedTooltip": { - "message": "Автозаповнення заблоковано на цьому вебсайті. Перевірте налаштування." + "autofillBlockedNoticeGuidance": { + "message": "Змінити в налаштуваннях" }, "websiteItemLabel": { "message": "Вебсайт $number$ (URI)", @@ -2805,17 +2805,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.'" }, "generateUsername": { @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "Пропозиції автозаповнення" }, + "itemSuggestions": { + "message": "Запропоновані записи" + }, "autofillSuggestionsTip": { "message": "Зберегти дані входу цього сайту для автозаповнення" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "Відправлення тексту" }, - "bitwardenNewLook": { - "message": "Bitwarden має новий вигляд!" - }, - "bitwardenNewLookDesc": { - "message": "Ще простіше автозаповнення та інтуїтивніший пошук у сховищі. Ознайомтеся!" - }, "accountActions": { "message": "Дії з обліковим записом" }, @@ -4671,22 +4668,22 @@ "message": "Вам не дозволено редагувати цей запис" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "Біометричне розблокування недоступне, оскільки спочатку потрібно ввести PIN-код або пароль." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "Біометричне розблокування наразі недоступне." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Біометричне розблокування недоступне через неправильно налаштовані системні файли." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Біометричне розблокування недоступне через неправильно налаштовані системні файли." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "Біометричне розблокування недоступне, оскільки програму Bitwarden для комп'ютера закрито." }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "Біометричне розблокування недоступне, оскільки воно не увімкнене для $EMAIL$ у програмі Bitwarden для комп'ютера.", "placeholders": { "email": { "content": "$1", @@ -4695,7 +4692,7 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "Біометричне розблокування зараз недоступне з невідомої причини." }, "authenticating": { "message": "Аутентифікація" diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 4ccdaf808f3..af33914d3e7 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Trang Web $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "Đã xóa mã khoá" }, "autofillSuggestions": { - "message": "Gợi ý điền tự động" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Lưu thông tin đăng nhập cho trang này để tự động điền" @@ -4586,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index dd6a2286c4a..14ad3927bec 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "将不会为这些网站提供自动填充和其他相关功能。您必须刷新页面才能使更改生效。" }, - "autofillBlockedNotice": { - "message": "该网站的自动填充功能已被阻止。请在设置中查看或更改。" + "autofillBlockedNoticeV2": { + "message": "该网站的自动填充已被屏蔽。" }, - "autofillBlockedTooltip": { - "message": "该网站的自动填充功能已被阻止。请在设置中查看。" + "autofillBlockedNoticeGuidance": { + "message": "在设置中更改它" }, "websiteItemLabel": { "message": "网站 $number$ (URI)", @@ -4010,6 +4010,9 @@ "autofillSuggestions": { "message": "自动填充建议" }, + "itemSuggestions": { + "message": "建议的项目" + }, "autofillSuggestionsTip": { "message": "将此站点保存为登录项目以用于自动填充" }, @@ -4586,12 +4589,6 @@ "textSends": { "message": "文本 Send" }, - "bitwardenNewLook": { - "message": "Bitwarden 拥有一个新的外观!" - }, - "bitwardenNewLookDesc": { - "message": "从密码库标签页自动填充和搜索比以往任何时候都更简单直观。来看看吧!" - }, "accountActions": { "message": "账户操作" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 467deffd815..d5903198e36 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "自動填入及其它相關的功能無法在這些網站上使用。您必須重新整理頁面來使變更生效。" }, - "autofillBlockedNotice": { - "message": "自動填入已在此網站被封鎖。請在設定中檢視或更改此限制。" + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "自動填入已在此網站被封鎖。請在設定中檢視。" + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "網站 $number$ (URI)", @@ -4008,7 +4008,10 @@ "message": "密碼金鑰已移除" }, "autofillSuggestions": { - "message": "自動填入建議" + "message": "Autofill suggestions" + }, + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "對此網站儲存登入項目為自動填入" @@ -4586,12 +4589,6 @@ "textSends": { "message": "文字 Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden 有了新外觀!" - }, - "bitwardenNewLookDesc": { - "message": "更容易使用的自動填入及密碼庫搜尋體驗。試試看吧!" - }, "accountActions": { "message": "帳號動作" }, diff --git a/apps/browser/store/locales/ru/copy.resx b/apps/browser/store/locales/ru/copy.resx index 7c9480567e8..f67462c7502 100644 --- a/apps/browser/store/locales/ru/copy.resx +++ b/apps/browser/store/locales/ru/copy.resx @@ -124,48 +124,47 @@ Дома, на работе или в пути - Bitwarden всегда защитит ваши пароли, passkeys и конфиденциальную информацию. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Признан лучшим менеджером паролей по версии PCMag, WIRED, The Verge, CNET, G2 и других! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +ЗАЩИТИТЕ СВОЮ ЦИФРОВУЮ ЖИЗНЬ +Защитите свою цифровую жизнь и защитите её от утечек данных, создавая уникальные, надёжные пароли для каждой учетной записи. Сохраните всё в зашифрованном сквозным шифрованием хранилище паролей, доступ к которому есть только у вас. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +ДОСТУП К СВОИМ ДАННЫМ В ЛЮБОМ МЕСТЕ, В ЛЮБОЕ ВРЕМЯ, НА ЛЮБОМ УСТРОЙСТВЕ +Легко управляйте, храните, защищайте и делитесь неограниченным количеством паролей на неограниченном количестве устройств без ограничений. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +КАЖДЫЙ ДОЛЖЕН ИМЕТЬ ИНСТРУМЕНТЫ ДЛЯ БЕЗОПАСНОСТИ В СЕТИ +Используйте Bitwarden бесплатно без рекламы или продажи данных. Bitwarden считает, что каждый должен иметь возможность оставаться в безопасности в сети. Премиум-планы предлагают доступ к расширенным функциям. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +РАСШИРЯЙТЕ ВОЗМОЖНОСТИ СВОИХ КОМАНД С ПОМОЩЬЮ BITWARDEN +Планы для Teams и Enterprise включают профессиональные бизнес-функции. Вот несколько примеров: интеграция SSO, собственный хостинг, интеграция каталогов и SCIM, глобальные политики, доступ через API, журналы событий и многое другое. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Используйте Bitwarden для защиты своих сотрудников и обмена конфиденциальной информацией с коллегами. +Дополнительные причины выбрать Bitwarden: -More reasons to choose Bitwarden: +Шифрование мирового класса +Пароли защищены усовершенствованным сквозным шифрованием (AES-256, хэштег salt и PBKDF2 SHA-256), поэтому ваши данные остаются в безопасности и конфиденциальности. -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Сторонние аудиты +Bitwarden регулярно проводит комплексные сторонние аудиты безопасности с известными фирмами по безопасности. Эти ежегодные аудиты включают оценку исходного кода и тестирование на проникновение по IP-адресам Bitwarden, серверам и веб-приложениям. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. - -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Расширенная 2FA +Защитите свой вход с помощью стороннего аутентификатора, кодов, отправленных по электронной почте, или учетных данных FIDO2 WebAuthn, таких как аппаратный ключ безопасности или ключ доступа. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Передавайте данные другим без посредников, сохраняя сквозное шифрование и ограничивая раскрытие информации. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +Встроенный генератор паролей +Создавайте длинные, сложные и уникальные пароли и имена пользователей для каждого посещаемого вами сайта. Интегрируйтесь с поставщиками псевдонимов электронной почты для дополнительной конфиденциальности. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Многоязычный перевод +Bitwarden переведён на более чем 60 языков, с помощью мирового сообщества через Crowdin. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Кроссплатформенные приложения +Защищайте и делитесь конфиденциальными данными в вашем хранилище Bitwarden из любого браузера, мобильного устройства или настольной ОС и т. д. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden защищает не только пароли +Решения со сквозным шифрованием для управления учётными данными от Bitwarden позволяют организациям защищать всё, включая секреты разработчиков и ключи доступа. Посетите Bitwarden.com, чтобы узнать больше о Bitwarden Secrets Manager и Bitwarden Passwordless.dev! From 2726b3a957b087def091fea57a2a28b1c54ba717 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 17 Jan 2025 11:22:03 +0100 Subject: [PATCH 235/270] Add no-sandbox to make electron work on ubuntu (#12908) --- apps/desktop/scripts/start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index d2c984a6f24..0e11ebd9083 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -25,7 +25,7 @@ concurrently( }, { name: "Elec", - command: `npx wait-on ./build/main.js && npx electron --inspect=5858 ${args.join( + command: `npx wait-on ./build/main.js && npx electron --no-sandbox --inspect=5858 ${args.join( " ", )} ./build --watch`, prefixColor: "green", From dafeb1492a6319326df32c6b233f65c41b0771cf Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 17 Jan 2025 14:57:31 +0100 Subject: [PATCH 236/270] Add type=button to stories (#12930) --- .../platform/popup/layout/popup-layout.stories.ts | 6 +++--- .../src/directives/copy-click.directive.spec.ts | 14 ++++++++++---- .../src/dialog/dialog.service.stories.ts | 8 +++++--- .../simple-configurable-dialog.service.stories.ts | 1 + .../simple-dialog/simple-dialog.service.stories.ts | 8 +++++--- .../components/kitchen-sink-main.component.ts | 10 +++++----- .../components/kitchen-sink-table.component.ts | 12 ++++++++++-- .../kitchen-sink-toggle-list.component.ts | 2 +- libs/components/src/table/sortable.component.ts | 7 ++++++- 9 files changed, 46 insertions(+), 22 deletions(-) diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index c1ac8823261..6a2d8162c7f 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -45,7 +45,7 @@ class ExtensionContainerComponent {} - - + + diff --git a/libs/angular/src/directives/copy-click.directive.spec.ts b/libs/angular/src/directives/copy-click.directive.spec.ts index 29466f7fbe3..09161ee261d 100644 --- a/libs/angular/src/directives/copy-click.directive.spec.ts +++ b/libs/angular/src/directives/copy-click.directive.spec.ts @@ -9,10 +9,16 @@ import { CopyClickDirective } from "./copy-click.directive"; @Component({ template: ` - - - - + + + + `, }) class TestCopyClickComponent { diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 5e938412804..2b42faeccca 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -19,7 +19,7 @@ interface Animal { } @Component({ - template: ``, + template: ``, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -42,8 +42,10 @@ class StoryDialogComponent { Animal: {{ animal }} - - + + `, diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts index 4f21b8611b3..9953fdd24ea 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts @@ -16,6 +16,7 @@ import { DialogModule } from "../../dialog.module";

    {{ group.title }}

    `, + template: ``, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -41,8 +41,10 @@ class StoryDialogComponent { Animal: {{ animal }} - - + + `, diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index 13f0a16a4d7..568c78566f6 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -15,8 +15,8 @@ import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component"; Dialog body text goes here. - - + + `, @@ -42,7 +42,7 @@ class KitchenSinkDialog {

    -
    +

    Bitwarden Kitchen Sink

    Learn more
    @@ -68,8 +68,8 @@ class KitchenSinkDialog {

    About

    - - + +

    Companies using Bitwarden

    diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts index 3c6d6f11444..ba71483d7de 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts @@ -20,7 +20,11 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; Password Manager Everyone - + Anchor link Another link @@ -33,7 +37,11 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; Secrets Manager Developers - + Anchor link Another link diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts index 2804c9e8351..6f0054912cf 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts @@ -7,7 +7,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; selector: "bit-kitchen-sink-toggle-list", imports: [KitchenSinkSharedModule], template: ` -
    +
    All 3 diff --git a/libs/components/src/table/sortable.component.ts b/libs/components/src/table/sortable.component.ts index d3309c03aa9..bdfb87ac52f 100644 --- a/libs/components/src/table/sortable.component.ts +++ b/libs/components/src/table/sortable.component.ts @@ -10,7 +10,12 @@ import { TableComponent } from "./table.component"; @Component({ selector: "th[bitSortable]", template: ` - From 9eecfbc8afad3d2b8ce5a066f57c713363f30853 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:56:09 +0100 Subject: [PATCH 237/270] [PM-17189] Remove LP fileless importer (#12918) * Remove LP fileless importer - Remove content scripts - Remove additions to message handlers and notifcation queue - Remove UI elements for the importer - Remove the actual importer code - Remove unsued keys from en/messages.json Remove feature flag "browser-fileless- import" Update webpack.config and manifest files to no longer include content scripts * Move feature flag idp-auto-submit-login under autofill grouping --------- Co-authored-by: Daniel James Smith --- apps/browser/src/_locales/en/messages.json | 32 -- .../abstractions/notification.background.ts | 9 +- .../background/notification.background.ts | 55 --- .../components/notification/container.ts | 2 - .../notification-queue-message-type.enum.ts | 1 - .../abstractions/notification-bar.ts | 1 - .../src/autofill/notification/bar.html | 10 - apps/browser/src/autofill/notification/bar.ts | 77 ---- .../browser/src/background/main.background.ts | 13 - apps/browser/src/manifest.json | 7 - apps/browser/src/manifest.v3.json | 6 - .../fileless-importer.background.ts | 34 -- .../fileless-importer.background.spec.ts | 339 ------------------ .../fileless-importer.background.ts | 265 -------------- .../fileless-importer-injected-scripts.ts | 25 -- .../abstractions/lp-fileless-importer.ts | 25 -- .../content/lp-fileless-importer.spec.ts | 211 ----------- .../src/tools/content/lp-fileless-importer.ts | 158 -------- ...-import-download-script-append.mv2.spec.ts | 23 -- ...press-import-download-script-append.mv2.ts | 9 - .../lp-suppress-import-download.spec.ts | 83 ----- .../content/lp-suppress-import-download.ts | 52 --- .../src/tools/enums/fileless-import.enums.ts | 12 - apps/browser/webpack.config.js | 4 - libs/common/src/enums/feature-flag.enum.ts | 6 +- 25 files changed, 3 insertions(+), 1456 deletions(-) delete mode 100644 apps/browser/src/tools/background/abstractions/fileless-importer.background.ts delete mode 100644 apps/browser/src/tools/background/fileless-importer.background.spec.ts delete mode 100644 apps/browser/src/tools/background/fileless-importer.background.ts delete mode 100644 apps/browser/src/tools/config/fileless-importer-injected-scripts.ts delete mode 100644 apps/browser/src/tools/content/abstractions/lp-fileless-importer.ts delete mode 100644 apps/browser/src/tools/content/lp-fileless-importer.spec.ts delete mode 100644 apps/browser/src/tools/content/lp-fileless-importer.ts delete mode 100644 apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts delete mode 100644 apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.ts delete mode 100644 apps/browser/src/tools/content/lp-suppress-import-download.spec.ts delete mode 100644 apps/browser/src/tools/content/lp-suppress-import-download.ts delete mode 100644 apps/browser/src/tools/enums/fileless-import.enums.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 51e1203673b..ecb47843df2 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index ed9d8e6d84b..1b989283112 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -31,16 +31,10 @@ interface AddUnlockVaultQueueMessage extends NotificationQueueMessage { type: "unlock"; } -interface AddRequestFilelessImportQueueMessage extends NotificationQueueMessage { - type: "fileless-import"; - importType?: string; -} - type NotificationQueueMessageItem = | AddLoginQueueMessage | AddChangePasswordQueueMessage - | AddUnlockVaultQueueMessage - | AddRequestFilelessImportQueueMessage; + | AddUnlockVaultQueueMessage; type LockedVaultPendingNotificationsData = { commandToRetry: { @@ -122,7 +116,6 @@ export { AddChangePasswordQueueMessage, AddLoginQueueMessage, AddUnlockVaultQueueMessage, - AddRequestFilelessImportQueueMessage, NotificationQueueMessageItem, LockedVaultPendingNotificationsData, AdjustNotificationBarMessageData, diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 5c6ff3c2c8c..0175b27bd69 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -37,7 +37,6 @@ import { AutofillService } from "../services/abstractions/autofill.service"; import { AddChangePasswordQueueMessage, AddLoginQueueMessage, - AddRequestFilelessImportQueueMessage, AddUnlockVaultQueueMessage, ChangePasswordMessageData, AddLoginMessageData, @@ -201,11 +200,6 @@ export default class NotificationBackground { case NotificationQueueMessageType.AddLogin: typeData.removeIndividualVault = await this.removeIndividualVault(); break; - case NotificationQueueMessageType.RequestFilelessImport: - typeData.importType = ( - notificationQueueMessage as AddRequestFilelessImportQueueMessage - ).importType; - break; } await BrowserApi.tabSendMessageData(tab, "openNotificationBar", { @@ -399,25 +393,6 @@ export default class NotificationBackground { } } - /** - * Sets up a notification to request a fileless import when the user - * attempts to trigger an import from a third party website. - * - * @param tab - The tab that we are sending the notification to - * @param importType - The type of import that is being requested - */ - async requestFilelessImport(tab: chrome.tabs.Tab, importType: string) { - const currentAuthStatus = await this.getAuthStatus(); - if (currentAuthStatus !== AuthenticationStatus.Unlocked || this.notificationQueue.length) { - return; - } - - const loginDomain = Utils.getDomain(tab.url); - if (loginDomain) { - await this.pushRequestFilelessImportToQueue(loginDomain, tab, importType); - } - } - private async pushChangePasswordToQueue( cipherId: string, loginDomain: string, @@ -456,36 +431,6 @@ export default class NotificationBackground { await this.sendNotificationQueueMessage(tab, message); } - /** - * Pushes a request to start a fileless import to the notification queue. - * This will display a notification bar to the user, prompting them to - * start the import. - * - * @param loginDomain - The domain of the tab that we are sending the notification to - * @param tab - The tab that we are sending the notification to - * @param importType - The type of import that is being requested - */ - private async pushRequestFilelessImportToQueue( - loginDomain: string, - tab: chrome.tabs.Tab, - importType?: string, - ) { - this.removeTabFromNotificationQueue(tab); - const launchTimestamp = new Date().getTime(); - const message: AddRequestFilelessImportQueueMessage = { - type: NotificationQueueMessageType.RequestFilelessImport, - domain: loginDomain, - tab, - launchTimestamp, - expires: new Date(launchTimestamp + 0.5 * 60000), // 30 seconds - wasVaultLocked: false, - importType, - }; - this.notificationQueue.push(message); - await this.checkNotificationQueue(tab); - this.removeTabFromNotificationQueue(tab); - } - /** * Saves a cipher based on the message sent from the notification bar. If the vault * is locked, the message will be added to the notification queue and the unlock diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index 0cce066cf3a..8bd07ab8296 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -91,8 +91,6 @@ function getHeaderMessage(i18n: { [key: string]: string }, type?: NotificationTy return i18n.updateLoginPrompt; case NotificationTypes.Unlock: return ""; - case NotificationTypes.FilelessImport: - return ""; default: return undefined; } diff --git a/apps/browser/src/autofill/enums/notification-queue-message-type.enum.ts b/apps/browser/src/autofill/enums/notification-queue-message-type.enum.ts index 1f5abac92b1..5a7b8fa990b 100644 --- a/apps/browser/src/autofill/enums/notification-queue-message-type.enum.ts +++ b/apps/browser/src/autofill/enums/notification-queue-message-type.enum.ts @@ -2,7 +2,6 @@ const NotificationQueueMessageType = { AddLogin: "add", ChangePassword: "change", UnlockVault: "unlock", - RequestFilelessImport: "fileless-import", } as const; type NotificationQueueMessageTypes = diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index 2e38adacb32..53948a26a2e 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -4,7 +4,6 @@ const NotificationTypes = { Add: "add", Change: "change", Unlock: "unlock", - FilelessImport: "fileless-import", } as const; type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]; diff --git a/apps/browser/src/autofill/notification/bar.html b/apps/browser/src/autofill/notification/bar.html index 6b0e76b5169..b686e1ec2f5 100644 --- a/apps/browser/src/autofill/notification/bar.html +++ b/apps/browser/src/autofill/notification/bar.html @@ -55,14 +55,4 @@
    - - diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 2c0ebe8e8e7..3fc61c448fe 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -4,7 +4,6 @@ import { ThemeTypes } from "@bitwarden/common/platform/enums"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { FilelessImportPort, FilelessImportType } from "../../tools/enums/fileless-import.enums"; import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background"; import { buildSvgDomElement } from "../utils"; import { circleCheckIcon } from "../utils/svg-icons"; @@ -59,11 +58,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) { notificationChangeDesc: chrome.i18n.getMessage("notificationChangeDesc"), notificationUnlock: chrome.i18n.getMessage("notificationUnlock"), notificationUnlockDesc: chrome.i18n.getMessage("notificationUnlockDesc"), - filelessImport: chrome.i18n.getMessage("filelessImport"), - lpFilelessImport: chrome.i18n.getMessage("lpFilelessImport"), - cancelFilelessImport: chrome.i18n.getMessage("no"), - lpCancelFilelessImport: chrome.i18n.getMessage("lpCancelFilelessImport"), - startFilelessImport: chrome.i18n.getMessage("startFilelessImport"), }; setupLogoLink(i18n); @@ -107,22 +101,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) { unlockTemplate.content.getElementById("unlock-text").textContent = i18n.notificationUnlockDesc; - // i18n for "Fileless Import" (fileless-import) template - const isLpImport = initData.importType === FilelessImportType.LP; - const importTemplate = document.getElementById("template-fileless-import") as HTMLTemplateElement; - - const startImportButton = importTemplate.content.getElementById("start-fileless-import"); - startImportButton.textContent = i18n.startFilelessImport; - - const cancelImportButton = importTemplate.content.getElementById("cancel-fileless-import"); - cancelImportButton.textContent = isLpImport - ? i18n.lpCancelFilelessImport - : i18n.cancelFilelessImport; - - importTemplate.content.getElementById("fileless-import-text").textContent = isLpImport - ? i18n.lpFilelessImport - : i18n.filelessImport; - // i18n for body content const closeButton = document.getElementById("close-button"); closeButton.title = i18n.close; @@ -134,8 +112,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) { handleTypeChange(); } else if (notificationType === "unlock") { handleTypeUnlock(); - } else if (notificationType === "fileless-import") { - handleTypeFilelessImport(); } closeButton.addEventListener("click", (e) => { @@ -249,59 +225,6 @@ function handleTypeUnlock() { }); } -/** - * Sets up a port to communicate with the fileless importer content script. - * This connection to the background script is used to trigger the action of - * downloading the CSV file from the LP importer or importing the data into - * the Bitwarden vault. - */ -function handleTypeFilelessImport() { - const importType = notificationBarIframeInitData.importType; - const port = chrome.runtime.connect({ name: FilelessImportPort.NotificationBar }); - setContent(document.getElementById("template-fileless-import") as HTMLTemplateElement); - - const startFilelessImportButton = document.getElementById("start-fileless-import"); - const startFilelessImport = () => { - port.postMessage({ command: "startFilelessImport", importType }); - document.getElementById("fileless-import-buttons").textContent = - chrome.i18n.getMessage("importing"); - startFilelessImportButton.removeEventListener("click", startFilelessImport); - }; - startFilelessImportButton.addEventListener("click", startFilelessImport); - - const cancelFilelessImportButton = document.getElementById("cancel-fileless-import"); - cancelFilelessImportButton.addEventListener("click", () => { - port.postMessage({ command: "cancelFilelessImport", importType }); - }); - - const handlePortMessage = (msg: any) => { - if (msg.command !== "filelessImportCompleted" && msg.command !== "filelessImportFailed") { - return; - } - - port.disconnect(); - - const filelessImportButtons = document.getElementById("fileless-import-buttons"); - const notificationBarOuterWrapper = document.getElementById("notification-bar-outer-wrapper"); - - if (msg.command === "filelessImportCompleted") { - filelessImportButtons.textContent = chrome.i18n.getMessage("dataSuccessfullyImported"); - filelessImportButtons.prepend(buildSvgDomElement(circleCheckIcon)); - filelessImportButtons.classList.add("success-message"); - notificationBarOuterWrapper.classList.add("success-event"); - adjustHeight(); - return; - } - - filelessImportButtons.textContent = chrome.i18n.getMessage("dataImportFailed"); - filelessImportButtons.classList.add("error-message"); - notificationBarOuterWrapper.classList.add("error-event"); - adjustHeight(); - logService.error(`Error Encountered During Import: ${msg.importErrorMessage}`); - }; - port.onMessage.addListener(handlePortMessage); -} - function setContent(template: HTMLTemplateElement) { const content = document.getElementById("content"); while (content.firstChild) { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 5d09122bbd6..3a60cc52109 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -268,7 +268,6 @@ import { OffscreenStorageService } from "../platform/storage/offscreen-storage.s import { SyncServiceListener } from "../platform/sync/sync-service.listener"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; -import FilelessImporterBackground from "../tools/background/fileless-importer.background"; import { VaultFilterService } from "../vault/services/vault-filter.service"; import CommandsBackground from "./commands.background"; @@ -393,7 +392,6 @@ export default class MainBackground { private notificationBackground: NotificationBackground; private overlayBackground: OverlayBackgroundInterface; private overlayNotificationsBackground: OverlayNotificationsBackgroundInterface; - private filelessImporterBackground: FilelessImporterBackground; private runtimeBackground: RuntimeBackground; private tabsBackground: TabsBackground; private webRequestBackground: WebRequestBackground; @@ -1160,16 +1158,6 @@ export default class MainBackground { this.notificationBackground, ); - this.filelessImporterBackground = new FilelessImporterBackground( - this.configService, - this.authService, - this.policyService, - this.notificationBackground, - this.importService, - this.syncService, - this.scriptInjectorService, - ); - this.autoSubmitLoginBackground = new AutoSubmitLoginBackground( this.logService, this.autofillService, @@ -1296,7 +1284,6 @@ export default class MainBackground { await this.runtimeBackground.init(); await this.notificationBackground.init(); this.overlayNotificationsBackground.init(); - this.filelessImporterBackground.init(); this.commandsBackground.init(); this.contextMenusBackground?.init(); this.idleBackground.init(); diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 86ea0eebbc8..016bf6dfe4b 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -29,12 +29,6 @@ "matches": ["*://*/*", "file:///*"], "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" - }, - { - "all_frames": false, - "js": ["content/lp-fileless-importer.js"], - "matches": ["https://lastpass.com/export.php"], - "run_at": "document_start" } ], "background": { @@ -140,7 +134,6 @@ }, "web_accessible_resources": [ "content/fido2-page-script.js", - "content/lp-suppress-import-download.js", "notification/bar.html", "images/icon38.png", "images/icon38_locked.png", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index ae7c888eb9b..104036140bd 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -30,12 +30,6 @@ "matches": ["*://*/*", "file:///*"], "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" - }, - { - "all_frames": false, - "js": ["content/lp-fileless-importer.js"], - "matches": ["https://lastpass.com/export.php"], - "run_at": "document_start" } ], "background": { diff --git a/apps/browser/src/tools/background/abstractions/fileless-importer.background.ts b/apps/browser/src/tools/background/abstractions/fileless-importer.background.ts deleted file mode 100644 index 2ade5bf7672..00000000000 --- a/apps/browser/src/tools/background/abstractions/fileless-importer.background.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { FilelessImportTypeKeys } from "../../enums/fileless-import.enums"; - -type FilelessImportPortMessage = { - command?: string; - importType?: FilelessImportTypeKeys; - data?: string; -}; - -type FilelessImportPortMessageHandlerParams = { - message: FilelessImportPortMessage; - port: chrome.runtime.Port; -}; - -type ImportNotificationMessageHandlers = { - [key: string]: ({ message, port }: FilelessImportPortMessageHandlerParams) => void; - cancelFilelessImport: ({ message, port }: FilelessImportPortMessageHandlerParams) => void; -}; - -type LpImporterMessageHandlers = { - [key: string]: ({ message, port }: FilelessImportPortMessageHandlerParams) => void; - displayLpImportNotification: ({ port }: { port: chrome.runtime.Port }) => void; - startLpImport: ({ message }: { message: FilelessImportPortMessage }) => void; -}; - -interface FilelessImporterBackground { - init(): void; -} - -export { - FilelessImportPortMessage, - ImportNotificationMessageHandlers, - LpImporterMessageHandlers, - FilelessImporterBackground, -}; diff --git a/apps/browser/src/tools/background/fileless-importer.background.spec.ts b/apps/browser/src/tools/background/fileless-importer.background.spec.ts deleted file mode 100644 index 409fac9790f..00000000000 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ /dev/null @@ -1,339 +0,0 @@ -import { mock } from "jest-mock-extended"; -import { of } from "rxjs"; - -import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { AuthService } from "@bitwarden/common/auth/services/auth.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { Importer, ImportResult, ImportServiceAbstraction } from "@bitwarden/importer/core"; - -import NotificationBackground from "../../autofill/background/notification.background"; -import { createPortSpyMock } from "../../autofill/spec/autofill-mocks"; -import { - flushPromises, - sendPortMessage, - triggerRuntimeOnConnectEvent, -} from "../../autofill/spec/testing-utils"; -import { BrowserApi } from "../../platform/browser/browser-api"; -import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; -import { FilelessImportPort, FilelessImportType } from "../enums/fileless-import.enums"; - -import FilelessImporterBackground from "./fileless-importer.background"; - -describe("FilelessImporterBackground ", () => { - let filelessImporterBackground: FilelessImporterBackground; - const configService = mock(); - const domainSettingsService = mock(); - const authService = mock(); - const policyService = mock(); - const notificationBackground = mock(); - const importService = mock(); - const syncService = mock(); - const platformUtilsService = mock(); - const logService = mock(); - let scriptInjectorService: BrowserScriptInjectorService; - let tabMock: chrome.tabs.Tab; - - beforeEach(() => { - domainSettingsService.blockedInteractionsUris$ = of({}); - policyService.policyAppliesToActiveUser$.mockImplementation(() => of(true)); - scriptInjectorService = new BrowserScriptInjectorService( - domainSettingsService, - platformUtilsService, - logService, - ); - filelessImporterBackground = new FilelessImporterBackground( - configService, - authService, - policyService, - notificationBackground, - importService, - syncService, - scriptInjectorService, - ); - filelessImporterBackground.init(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("init", () => { - it("sets up the port message listeners on initialization of the class", () => { - expect(chrome.runtime.onConnect.addListener).toHaveBeenCalledWith(expect.any(Function)); - }); - }); - - describe("handle ports onConnect", () => { - let lpImporterPort: chrome.runtime.Port; - let manifestVersionSpy: jest.SpyInstance; - let executeScriptInTabSpy: jest.SpyInstance; - - beforeEach(() => { - lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter); - tabMock = lpImporterPort.sender.tab; - jest.spyOn(BrowserApi, "getTab").mockImplementation(async () => tabMock); - manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); - executeScriptInTabSpy = jest.spyOn(BrowserApi, "executeScriptInTab").mockResolvedValue(null); - jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked); - jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true); - jest.spyOn(filelessImporterBackground as any, "removeIndividualVault"); - }); - - it("ignores the port connection if the port name is not present in the set of filelessImportNames", async () => { - const port = createPortSpyMock("some-other-port"); - - triggerRuntimeOnConnectEvent(port); - await flushPromises(); - - expect(port.postMessage).not.toHaveBeenCalled(); - }); - - it("posts a message to the port indicating that the fileless import feature is disabled if the user's auth status is not unlocked", async () => { - jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Locked); - - triggerRuntimeOnConnectEvent(lpImporterPort); - await flushPromises(); - - expect(lpImporterPort.postMessage).toHaveBeenCalledWith({ - command: "verifyFeatureFlag", - filelessImportEnabled: false, - }); - }); - - it("posts a message to the port indicating that the fileless import feature is disabled if the user's policy removes individual vaults", async () => { - triggerRuntimeOnConnectEvent(lpImporterPort); - await flushPromises(); - - expect(lpImporterPort.postMessage).toHaveBeenCalledWith({ - command: "verifyFeatureFlag", - filelessImportEnabled: false, - }); - }); - - it("posts a message to the port indicating that the fileless import feature is disabled if the feature flag is turned off", async () => { - jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(false); - - triggerRuntimeOnConnectEvent(lpImporterPort); - await flushPromises(); - - expect(lpImporterPort.postMessage).toHaveBeenCalledWith({ - command: "verifyFeatureFlag", - filelessImportEnabled: false, - }); - }); - - it("posts a message to the port indicating that the fileless import feature is enabled", async () => { - policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); - - triggerRuntimeOnConnectEvent(lpImporterPort); - await flushPromises(); - - expect(lpImporterPort.postMessage).toHaveBeenCalledWith({ - command: "verifyFeatureFlag", - filelessImportEnabled: true, - }); - }); - - it("triggers an injection of the `lp-suppress-import-download.js` script in manifest v3", async () => { - policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); - manifestVersionSpy.mockReturnValue(3); - - triggerRuntimeOnConnectEvent(lpImporterPort); - await flushPromises(); - - expect(executeScriptInTabSpy).toHaveBeenCalledWith( - lpImporterPort.sender.tab.id, - { file: "content/lp-suppress-import-download.js", runAt: "document_start", frameId: 0 }, - { world: "MAIN" }, - ); - }); - - it("triggers an injection of the `lp-suppress-import-download-script-append-mv2.js` script in manifest v2", async () => { - policyService.policyAppliesToActiveUser$.mockImplementationOnce(() => of(false)); - manifestVersionSpy.mockReturnValue(2); - - triggerRuntimeOnConnectEvent(lpImporterPort); - await flushPromises(); - - expect(executeScriptInTabSpy).toHaveBeenCalledWith(lpImporterPort.sender.tab.id, { - file: "content/lp-suppress-import-download-script-append-mv2.js", - runAt: "document_start", - frameId: 0, - }); - }); - }); - - describe("port messages", () => { - let notificationPort: chrome.runtime.Port; - let lpImporterPort: chrome.runtime.Port; - - beforeEach(async () => { - policyService.policyAppliesToActiveUser$.mockImplementation(() => of(false)); - jest.spyOn(authService, "getAuthStatus").mockResolvedValue(AuthenticationStatus.Unlocked); - jest.spyOn(configService, "getFeatureFlag").mockResolvedValue(true); - - triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.NotificationBar)); - triggerRuntimeOnConnectEvent(createPortSpyMock(FilelessImportPort.LpImporter)); - await flushPromises(); - notificationPort = filelessImporterBackground["importNotificationsPort"]; - lpImporterPort = filelessImporterBackground["lpImporterPort"]; - }); - - it("skips handling a message if a message handler is not associated with the port message command", () => { - sendPortMessage(notificationPort, { command: "commandNotFound" }); - - expect(chrome.tabs.sendMessage).not.toHaveBeenCalled(); - }); - - describe("import notification port messages", () => { - describe("startFilelessImport", () => { - it("sends a message to start the LastPass fileless import within the content script", () => { - sendPortMessage(notificationPort, { - command: "startFilelessImport", - importType: FilelessImportType.LP, - }); - - expect(lpImporterPort.postMessage).toHaveBeenCalledWith({ - command: "startLpFilelessImport", - }); - }); - }); - - describe("cancelFilelessImport", () => { - it("sends a message to close the notification bar", async () => { - sendPortMessage(notificationPort, { command: "cancelFilelessImport" }); - - expect(chrome.tabs.sendMessage).toHaveBeenCalledWith( - notificationPort.sender.tab.id, - { - command: "closeNotificationBar", - }, - null, - expect.anything(), - ); - expect(lpImporterPort.postMessage).not.toHaveBeenCalledWith({ - command: "triggerCsvDownload", - }); - }); - - it("sends a message to trigger a download of the LP importer CSV", () => { - sendPortMessage(notificationPort, { - command: "cancelFilelessImport", - importType: FilelessImportType.LP, - }); - - expect(lpImporterPort.postMessage).toHaveBeenCalledWith({ - command: "triggerCsvDownload", - }); - expect(lpImporterPort.disconnect).toHaveBeenCalled(); - }); - }); - }); - - describe("lp importer port messages", () => { - describe("displayLpImportNotification", () => { - it("creates a request fileless import notification", async () => { - jest.spyOn(filelessImporterBackground["notificationBackground"], "requestFilelessImport"); - - sendPortMessage(lpImporterPort, { - command: "displayLpImportNotification", - }); - await flushPromises(); - - expect( - filelessImporterBackground["notificationBackground"].requestFilelessImport, - ).toHaveBeenCalledWith(lpImporterPort.sender.tab, FilelessImportType.LP); - }); - }); - - describe("startLpImport", () => { - it("ignores the message if the message does not contain data", () => { - sendPortMessage(lpImporterPort, { - command: "startLpImport", - }); - - expect(filelessImporterBackground["importService"].import).not.toHaveBeenCalled(); - }); - - it("triggers the import of the LastPass vault", async () => { - const data = "url,username,password"; - const importer = mock(); - jest - .spyOn(filelessImporterBackground["importService"], "getImporter") - .mockReturnValue(importer); - jest.spyOn(filelessImporterBackground["importService"], "import").mockResolvedValue( - mock({ - success: true, - }), - ); - jest.spyOn(filelessImporterBackground["syncService"], "fullSync"); - - sendPortMessage(lpImporterPort, { - command: "startLpImport", - data, - }); - await flushPromises(); - - expect(filelessImporterBackground["importService"].import).toHaveBeenCalledWith( - importer, - data, - null, - null, - false, - ); - expect( - filelessImporterBackground["importNotificationsPort"].postMessage, - ).toHaveBeenCalledWith({ command: "filelessImportCompleted" }); - expect(filelessImporterBackground["syncService"].fullSync).toHaveBeenCalledWith(true); - }); - - it("posts a failed message if the import fails", async () => { - const data = "url,username,password"; - const importer = mock(); - jest - .spyOn(filelessImporterBackground["importService"], "getImporter") - .mockReturnValue(importer); - jest - .spyOn(filelessImporterBackground["importService"], "import") - .mockImplementation(() => { - throw new Error("error"); - }); - jest.spyOn(filelessImporterBackground["syncService"], "fullSync"); - - sendPortMessage(lpImporterPort, { - command: "startLpImport", - data, - }); - await flushPromises(); - - expect( - filelessImporterBackground["importNotificationsPort"].postMessage, - ).toHaveBeenCalledWith({ command: "filelessImportFailed" }); - }); - }); - }); - }); - - describe("handleImporterPortDisconnect", () => { - it("resets the port properties to null", () => { - const lpImporterPort = createPortSpyMock(FilelessImportPort.LpImporter); - const notificationPort = createPortSpyMock(FilelessImportPort.NotificationBar); - filelessImporterBackground["lpImporterPort"] = lpImporterPort; - filelessImporterBackground["importNotificationsPort"] = notificationPort; - - filelessImporterBackground["handleImporterPortDisconnect"](lpImporterPort); - - expect(filelessImporterBackground["lpImporterPort"]).toBeNull(); - expect(filelessImporterBackground["importNotificationsPort"]).not.toBeNull(); - - filelessImporterBackground["handleImporterPortDisconnect"](notificationPort); - - expect(filelessImporterBackground["importNotificationsPort"]).toBeNull(); - }); - }); -}); diff --git a/apps/browser/src/tools/background/fileless-importer.background.ts b/apps/browser/src/tools/background/fileless-importer.background.ts deleted file mode 100644 index 21d597ec8ae..00000000000 --- a/apps/browser/src/tools/background/fileless-importer.background.ts +++ /dev/null @@ -1,265 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { firstValueFrom } from "rxjs"; - -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ImportServiceAbstraction } from "@bitwarden/importer/core"; - -import NotificationBackground from "../../autofill/background/notification.background"; -import { BrowserApi } from "../../platform/browser/browser-api"; -import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; -import { FilelessImporterInjectedScriptsConfig } from "../config/fileless-importer-injected-scripts"; -import { - FilelessImportPort, - FilelessImportType, - FilelessImportTypeKeys, -} from "../enums/fileless-import.enums"; - -import { - ImportNotificationMessageHandlers, - LpImporterMessageHandlers, - FilelessImporterBackground as FilelessImporterBackgroundInterface, - FilelessImportPortMessage, -} from "./abstractions/fileless-importer.background"; - -class FilelessImporterBackground implements FilelessImporterBackgroundInterface { - private static readonly filelessImporterPortNames: Set = new Set([ - FilelessImportPort.LpImporter, - FilelessImportPort.NotificationBar, - ]); - private importNotificationsPort: chrome.runtime.Port; - private lpImporterPort: chrome.runtime.Port; - private readonly importNotificationsPortMessageHandlers: ImportNotificationMessageHandlers = { - startFilelessImport: ({ message }) => this.startFilelessImport(message.importType), - cancelFilelessImport: ({ message, port }) => - this.cancelFilelessImport(message.importType, port.sender), - }; - private readonly lpImporterPortMessageHandlers: LpImporterMessageHandlers = { - displayLpImportNotification: ({ port }) => - this.displayFilelessImportNotification(port.sender.tab, FilelessImportType.LP), - startLpImport: ({ message }) => this.triggerLpImport(message.data), - }; - - /** - * Creates a new instance of the fileless importer background logic. - * - * @param configService - Identifies if the feature flag is enabled. - * @param authService - Verifies if the auth status of the user. - * @param policyService - Identifies if the user account has a policy that disables personal ownership. - * @param notificationBackground - Used to inject the notification bar into the tab. - * @param importService - Used to import the export data into the vault. - * @param syncService - Used to trigger a full sync after the import is completed. - * @param scriptInjectorService - Used to inject content scripts that initialize the import process - */ - constructor( - private configService: ConfigService, - private authService: AuthService, - private policyService: PolicyService, - private notificationBackground: NotificationBackground, - private importService: ImportServiceAbstraction, - private syncService: SyncService, - private scriptInjectorService: ScriptInjectorService, - ) {} - - /** - * Initializes the fileless importer background logic. - */ - init() { - this.setupPortMessageListeners(); - } - - /** - * Starts an import of the export data pulled from the tab. - * - * @param importType - The type of import to start. Identifies the used content script. - */ - private startFilelessImport(importType: FilelessImportTypeKeys) { - if (importType === FilelessImportType.LP) { - this.lpImporterPort?.postMessage({ command: "startLpFilelessImport" }); - } - } - - /** - * Cancels an import of the export data pulled from the tab. This closes any - * existing notifications that are present in the tab, and triggers importer - * specific behavior based on the import type. - * - * @param importType - The type of import to cancel. Identifies the used content script. - * @param sender - The sender of the message. - */ - private async cancelFilelessImport( - importType: FilelessImportTypeKeys, - sender: chrome.runtime.MessageSender, - ) { - if (importType === FilelessImportType.LP) { - this.triggerLpImporterCsvDownload(); - } - - await BrowserApi.tabSendMessage(sender.tab, { command: "closeNotificationBar" }); - } - - /** - * Injects the notification bar into the passed tab. - * - * @param tab - * @param importType - */ - private async displayFilelessImportNotification(tab: chrome.tabs.Tab, importType: string) { - await this.notificationBackground.requestFilelessImport(tab, importType); - } - - /** - * Triggers the download of the CSV file from the LP importer. This is triggered - * when the user opts to not save the export to Bitwarden within the notification bar. - */ - private triggerLpImporterCsvDownload() { - this.lpImporterPort?.postMessage({ command: "triggerCsvDownload" }); - this.lpImporterPort?.disconnect(); - } - - /** - * Completes the import process for the LP importer. This is triggered when the - * user opts to save the export to Bitwarden within the notification bar. - * - * @param data - The export data to import. - * @param sender - The sender of the message. - */ - private async triggerLpImport(data: string) { - if (!data) { - return; - } - - const promptForPassword_callback = async () => ""; - const importer = this.importService.getImporter( - "lastpasscsv", - promptForPassword_callback, - null, - ); - - try { - const result = await this.importService.import(importer, data, null, null, false); - if (result.success) { - this.importNotificationsPort?.postMessage({ command: "filelessImportCompleted" }); - await this.syncService.fullSync(true); - } - } catch (error) { - this.importNotificationsPort?.postMessage({ - command: "filelessImportFailed", - importErrorMessage: Object.values(error).length - ? error - : chrome.i18n.getMessage("importNetworkError"), - }); - } - } - - /** - * Identifies if the user account has a policy that disables personal ownership. - */ - private async removeIndividualVault(): Promise { - return await firstValueFrom( - this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), - ); - } - - /** - * Sets up onConnect listeners for the extension. - */ - private setupPortMessageListeners() { - chrome.runtime.onConnect.addListener(this.handlePortOnConnect); - } - - /** - * Handles connections from content scripts that affect the fileless importer behavior. - * Is used to facilitate the passing of data and user actions to enact the import - * of web content to the Bitwarden vault. Along with this, a check is made to ensure - * that the feature flag is enabled and the user is authenticated. - */ - private handlePortOnConnect = async (port: chrome.runtime.Port) => { - if (!FilelessImporterBackground.filelessImporterPortNames.has(port.name)) { - return; - } - - const filelessImportFeatureFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.BrowserFilelessImport, - ); - const userAuthStatus = await this.authService.getAuthStatus(); - const removeIndividualVault = await this.removeIndividualVault(); - const filelessImportEnabled = - filelessImportFeatureFlagEnabled && - userAuthStatus === AuthenticationStatus.Unlocked && - !removeIndividualVault; - port.postMessage({ command: "verifyFeatureFlag", filelessImportEnabled }); - - if (!filelessImportEnabled) { - return; - } - - port.onMessage.addListener(this.handleImporterPortMessage); - port.onDisconnect.addListener(this.handleImporterPortDisconnect); - - switch (port.name) { - case FilelessImportPort.LpImporter: - this.lpImporterPort = port; - await this.scriptInjectorService.inject({ - tabId: port.sender.tab.id, - injectDetails: { runAt: "document_start" }, - mv2Details: FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv2, - mv3Details: FilelessImporterInjectedScriptsConfig.LpSuppressImportDownload.mv3, - }); - break; - case FilelessImportPort.NotificationBar: - this.importNotificationsPort = port; - break; - } - }; - - /** - * Handles messages that are sent from fileless importer content scripts. - * @param message - The message that was sent. - * @param port - The port that the message was sent from. - */ - private handleImporterPortMessage = ( - message: FilelessImportPortMessage, - port: chrome.runtime.Port, - ) => { - let handler: CallableFunction | undefined; - - switch (port.name) { - case FilelessImportPort.LpImporter: - handler = this.lpImporterPortMessageHandlers[message.command]; - break; - case FilelessImportPort.NotificationBar: - handler = this.importNotificationsPortMessageHandlers[message.command]; - break; - } - - if (!handler) { - return; - } - - handler({ message, port }); - }; - - /** - * Handles disconnections from fileless importer content scripts. - * @param port - The port that was disconnected. - */ - private handleImporterPortDisconnect = (port: chrome.runtime.Port) => { - switch (port.name) { - case FilelessImportPort.LpImporter: - this.lpImporterPort = null; - break; - case FilelessImportPort.NotificationBar: - this.importNotificationsPort = null; - break; - } - }; -} - -export default FilelessImporterBackground; diff --git a/apps/browser/src/tools/config/fileless-importer-injected-scripts.ts b/apps/browser/src/tools/config/fileless-importer-injected-scripts.ts deleted file mode 100644 index 898ee1205ab..00000000000 --- a/apps/browser/src/tools/config/fileless-importer-injected-scripts.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - Mv2ScriptInjectionDetails, - Mv3ScriptInjectionDetails, -} from "../../platform/services/abstractions/script-injector.service"; - -type FilelessImporterInjectedScriptsConfigurations = { - LpSuppressImportDownload: { - mv2: Mv2ScriptInjectionDetails; - mv3: Mv3ScriptInjectionDetails; - }; -}; - -const FilelessImporterInjectedScriptsConfig: FilelessImporterInjectedScriptsConfigurations = { - LpSuppressImportDownload: { - mv2: { - file: "content/lp-suppress-import-download-script-append-mv2.js", - }, - mv3: { - file: "content/lp-suppress-import-download.js", - world: "MAIN", - }, - }, -} as const; - -export { FilelessImporterInjectedScriptsConfig }; diff --git a/apps/browser/src/tools/content/abstractions/lp-fileless-importer.ts b/apps/browser/src/tools/content/abstractions/lp-fileless-importer.ts deleted file mode 100644 index 018ea2c8d94..00000000000 --- a/apps/browser/src/tools/content/abstractions/lp-fileless-importer.ts +++ /dev/null @@ -1,25 +0,0 @@ -type LpFilelessImporterMessage = { - command?: string; - data?: string; - filelessImportEnabled?: boolean; -}; - -type LpFilelessImporterMessageHandlerParams = { - message: LpFilelessImporterMessage; - port: chrome.runtime.Port; -}; - -type LpFilelessImporterMessageHandlers = { - [key: string]: ({ message, port }: LpFilelessImporterMessageHandlerParams) => void; - verifyFeatureFlag: ({ message }: { message: LpFilelessImporterMessage }) => void; - triggerCsvDownload: () => void; - startLpFilelessImport: () => void; -}; - -interface LpFilelessImporter { - init(): void; - handleFeatureFlagVerification(message: LpFilelessImporterMessage): void; - triggerCsvDownload(): void; -} - -export { LpFilelessImporterMessage, LpFilelessImporterMessageHandlers, LpFilelessImporter }; diff --git a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts deleted file mode 100644 index 21fa44b8d3f..00000000000 --- a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { mock } from "jest-mock-extended"; - -import { createPortSpyMock } from "../../autofill/spec/autofill-mocks"; -import { sendPortMessage } from "../../autofill/spec/testing-utils"; -import { FilelessImportPort } from "../enums/fileless-import.enums"; - -import { LpFilelessImporter } from "./abstractions/lp-fileless-importer"; - -describe("LpFilelessImporter", () => { - let lpFilelessImporter: LpFilelessImporter & { [key: string]: any }; - const portSpy: chrome.runtime.Port = createPortSpyMock(FilelessImportPort.LpImporter); - chrome.runtime.connect = jest.fn(() => portSpy); - - beforeEach(() => { - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-require-imports - require("./lp-fileless-importer"); - lpFilelessImporter = (globalThis as any).lpFilelessImporter; - }); - - afterEach(() => { - (globalThis as any).lpFilelessImporter = undefined; - jest.clearAllMocks(); - jest.resetModules(); - Object.defineProperty(document, "readyState", { - value: "complete", - writable: true, - }); - }); - - describe("init", () => { - it("sets up the port connection with the background script", () => { - lpFilelessImporter.init(); - - expect(chrome.runtime.connect).toHaveBeenCalledWith({ - name: FilelessImportPort.LpImporter, - }); - }); - }); - - describe("handleFeatureFlagVerification", () => { - it("disconnects the message port when the fileless import feature is disabled", () => { - lpFilelessImporter.handleFeatureFlagVerification({ filelessImportEnabled: false }); - - expect(portSpy.disconnect).toHaveBeenCalled(); - }); - - it("sets up an event listener for DOMContentLoaded that triggers the importer when the document ready state is `loading`", () => { - Object.defineProperty(document, "readyState", { - value: "loading", - writable: true, - }); - const message = { - command: "verifyFeatureFlag", - filelessImportEnabled: true, - }; - jest.spyOn(document, "addEventListener"); - - lpFilelessImporter.handleFeatureFlagVerification(message); - - expect(document.addEventListener).toHaveBeenCalledWith( - "DOMContentLoaded", - (lpFilelessImporter as any).loadImporter, - ); - }); - - it("sets up a mutation observer to watch the document body for injection of the export content", () => { - const message = { - command: "verifyFeatureFlag", - filelessImportEnabled: true, - }; - jest.spyOn(document, "addEventListener"); - jest.spyOn(window, "MutationObserver").mockImplementationOnce(() => mock()); - - lpFilelessImporter.handleFeatureFlagVerification(message); - - expect(window.MutationObserver).toHaveBeenCalledWith( - (lpFilelessImporter as any).handleMutation, - ); - expect((lpFilelessImporter as any).mutationObserver.observe).toHaveBeenCalledWith( - document.body, - { childList: true, subtree: true }, - ); - }); - }); - - describe("triggerCsvDownload", () => { - it("posts a window message that triggers the download of the LastPass export", () => { - jest.spyOn(globalThis, "postMessage"); - - lpFilelessImporter.triggerCsvDownload(); - - expect(globalThis.postMessage).toHaveBeenCalledWith( - { command: "triggerCsvDownload" }, - "https://lastpass.com", - ); - }); - }); - - describe("handleMutation", () => { - beforeEach(() => { - lpFilelessImporter["mutationObserver"] = mock({ disconnect: jest.fn() }); - jest.spyOn(portSpy, "postMessage"); - }); - - it("ignores mutations that contain empty records", () => { - lpFilelessImporter["handleMutation"]([]); - - expect(portSpy.postMessage).not.toHaveBeenCalled(); - }); - - it("ignores mutations that have no added nodes in the mutation", () => { - lpFilelessImporter["handleMutation"]([{ addedNodes: [] }]); - - expect(portSpy.postMessage).not.toHaveBeenCalled(); - }); - - it("ignores mutations that have no added nodes with a tagname of `pre`", () => { - lpFilelessImporter["handleMutation"]([{ addedNodes: [{ nodeName: "div" }] }]); - - expect(portSpy.postMessage).not.toHaveBeenCalled(); - }); - - it("ignores mutations where the found `pre` element does not contain any textContent", () => { - lpFilelessImporter["handleMutation"]([{ addedNodes: [{ nodeName: "pre" }] }]); - - expect(portSpy.postMessage).not.toHaveBeenCalled(); - }); - - it("ignores mutations where the found `pre` element does not contain the expected header content", () => { - lpFilelessImporter["handleMutation"]([ - { addedNodes: [{ nodeName: "pre", textContent: "some other content" }] }, - ]); - - expect(portSpy.postMessage).not.toHaveBeenCalled(); - }); - - it("will store the export data, display the import notification, and disconnect the mutation observer when the export data is appended", () => { - const observerDisconnectSpy = jest.spyOn( - lpFilelessImporter["mutationObserver"], - "disconnect", - ); - - lpFilelessImporter["handleMutation"]([ - { addedNodes: [{ nodeName: "pre", textContent: "url,username,password" }] }, - ]); - - expect(lpFilelessImporter["exportData"]).toEqual("url,username,password"); - expect(portSpy.postMessage).toHaveBeenCalledWith({ command: "displayLpImportNotification" }); - expect(observerDisconnectSpy).toHaveBeenCalled(); - }); - }); - - describe("handlePortMessage", () => { - it("ignores messages that are not registered with the portMessageHandlers", () => { - const message = { command: "unknownCommand" }; - jest.spyOn(lpFilelessImporter, "handleFeatureFlagVerification"); - jest.spyOn(lpFilelessImporter, "triggerCsvDownload"); - - sendPortMessage(portSpy, message); - - expect(lpFilelessImporter.handleFeatureFlagVerification).not.toHaveBeenCalled(); - expect(lpFilelessImporter.triggerCsvDownload).not.toHaveBeenCalled(); - }); - - it("handles the port message that verifies the fileless import feature flag", () => { - const message = { command: "verifyFeatureFlag", filelessImportEnabled: true }; - jest.spyOn(lpFilelessImporter, "handleFeatureFlagVerification").mockImplementation(); - - sendPortMessage(portSpy, message); - - expect(lpFilelessImporter.handleFeatureFlagVerification).toHaveBeenCalledWith(message); - }); - - it("handles the port message that triggers the LastPass csv download", () => { - const message = { command: "triggerCsvDownload" }; - jest.spyOn(lpFilelessImporter, "triggerCsvDownload"); - - sendPortMessage(portSpy, message); - - expect(lpFilelessImporter.triggerCsvDownload).toHaveBeenCalled(); - }); - - describe("handles the port message that triggers the LastPass fileless import", () => { - beforeEach(() => { - jest.spyOn(lpFilelessImporter as any, "postPortMessage"); - }); - - it("skips the import of the export data is not populated", () => { - const message = { command: "startLpFilelessImport" }; - - sendPortMessage(portSpy, message); - - expect(lpFilelessImporter.postPortMessage).not.toHaveBeenCalled(); - }); - - it("starts the last pass fileless import", () => { - const message = { command: "startLpFilelessImport" }; - const exportData = "url,username,password"; - lpFilelessImporter["exportData"] = exportData; - - sendPortMessage(portSpy, message); - - expect(lpFilelessImporter.postPortMessage).toHaveBeenCalledWith({ - command: "startLpImport", - data: exportData, - }); - }); - }); - }); -}); diff --git a/apps/browser/src/tools/content/lp-fileless-importer.ts b/apps/browser/src/tools/content/lp-fileless-importer.ts deleted file mode 100644 index 497a499b337..00000000000 --- a/apps/browser/src/tools/content/lp-fileless-importer.ts +++ /dev/null @@ -1,158 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { FilelessImportPort } from "../enums/fileless-import.enums"; - -import { - LpFilelessImporter as LpFilelessImporterInterface, - LpFilelessImporterMessage, - LpFilelessImporterMessageHandlers, -} from "./abstractions/lp-fileless-importer"; - -class LpFilelessImporter implements LpFilelessImporterInterface { - private exportData: string; - private messagePort: chrome.runtime.Port; - private mutationObserver: MutationObserver; - private readonly portMessageHandlers: LpFilelessImporterMessageHandlers = { - verifyFeatureFlag: ({ message }) => this.handleFeatureFlagVerification(message), - triggerCsvDownload: () => this.triggerCsvDownload(), - startLpFilelessImport: () => this.startLpImport(), - }; - - /** - * Initializes the LP fileless importer. - */ - init() { - this.setupMessagePort(); - } - - /** - * Enacts behavior based on the feature flag verification message. If the feature flag is - * not enabled, the message port is disconnected. If the feature flag is enabled, the - * download of the CSV file is suppressed. - * - * @param message - The port message, contains the feature flag indicator. - */ - handleFeatureFlagVerification(message: LpFilelessImporterMessage) { - if (!message.filelessImportEnabled) { - this.messagePort?.disconnect(); - return; - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", this.loadImporter); - return; - } - - this.loadImporter(); - } - - /** - * Posts a message to the LP importer to trigger the download of the CSV file. - */ - triggerCsvDownload() { - this.postWindowMessage({ command: "triggerCsvDownload" }); - } - - /** - * Initializes the importing mechanism used to import the CSV file into Bitwarden. - * This is done by observing the DOM for the addition of the LP importer element. - */ - private loadImporter = () => { - this.mutationObserver = new MutationObserver(this.handleMutation); - this.mutationObserver.observe(document.body, { - childList: true, - subtree: true, - }); - }; - - /** - * Handles mutations that are observed by the mutation observer. When the exported data - * element is added to the DOM, the export data is extracted and the import prompt is - * displayed. - * - * @param mutations - The mutations that were observed. - */ - private handleMutation = (mutations: MutationRecord[]) => { - let textContent: string; - for (let index = 0; index < mutations?.length; index++) { - const mutation: MutationRecord = mutations[index]; - - textContent = Array.from(mutation.addedNodes) - .filter((node) => node.nodeName.toLowerCase() === "pre") - .map((node) => (node as HTMLPreElement).textContent?.trim()) - .find((text) => text?.indexOf("url,username,password") >= 0); - - if (textContent) { - break; - } - } - - if (textContent) { - this.exportData = textContent; - this.postPortMessage({ command: "displayLpImportNotification" }); - this.mutationObserver.disconnect(); - } - }; - - /** - * If the export data is present, sends a message to the background with - * the export data to start the import process. - */ - private startLpImport() { - if (!this.exportData) { - return; - } - - this.postPortMessage({ command: "startLpImport", data: this.exportData }); - this.messagePort?.disconnect(); - } - - /** - * Posts a message to the background script. - * - * @param message - The message to post. - */ - private postPortMessage(message: LpFilelessImporterMessage) { - this.messagePort?.postMessage(message); - } - - /** - * Posts a message to the global context of the page. - * - * @param message - The message to post. - */ - private postWindowMessage(message: LpFilelessImporterMessage) { - globalThis.postMessage(message, "https://lastpass.com"); - } - - /** - * Sets up the message port that is used to facilitate communication between the - * background script and the content script. - */ - private setupMessagePort() { - this.messagePort = chrome.runtime.connect({ name: FilelessImportPort.LpImporter }); - this.messagePort.onMessage.addListener(this.handlePortMessage); - } - - /** - * Handles messages that are sent from the background script. - * - * @param message - The message that was sent. - * @param port - The port that the message was sent from. - */ - private handlePortMessage = (message: LpFilelessImporterMessage, port: chrome.runtime.Port) => { - const handler = this.portMessageHandlers[message.command]; - if (!handler) { - return; - } - - handler({ message, port }); - }; -} - -(function () { - if (!(globalThis as any).lpFilelessImporter) { - (globalThis as any).lpFilelessImporter = new LpFilelessImporter(); - (globalThis as any).lpFilelessImporter.init(); - } -})(); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts deleted file mode 100644 index 8479235cc17..00000000000 --- a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -describe("LP Suppress Import Download for Manifest v2", () => { - it("appends the `lp-suppress-import-download.js` script to the document element", () => { - let createdScriptElement: HTMLScriptElement; - jest.spyOn(window.document, "createElement"); - jest.spyOn(window.document.documentElement, "appendChild").mockImplementation((node) => { - createdScriptElement = node as HTMLScriptElement; - return node; - }); - - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-require-imports - require("./lp-suppress-import-download-script-append.mv2"); - - expect(window.document.createElement).toHaveBeenCalledWith("script"); - expect(chrome.runtime.getURL).toHaveBeenCalledWith("content/lp-suppress-import-download.js"); - expect(window.document.documentElement.appendChild).toHaveBeenCalledWith( - expect.any(HTMLScriptElement), - ); - expect(createdScriptElement.src).toBe( - "chrome-extension://id/content/lp-suppress-import-download.js", - ); - }); -}); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.ts b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.ts deleted file mode 100644 index cd641590ad1..00000000000 --- a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This script handles injection of the LP suppress import download script into the document. - * This is required for manifest v2, but will be removed when we migrate fully to manifest v3. - */ -(function (globalContext) { - const script = globalContext.document.createElement("script"); - script.src = chrome.runtime.getURL("content/lp-suppress-import-download.js"); - globalContext.document.documentElement.appendChild(script); -})(window); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts deleted file mode 100644 index ff0ed381599..00000000000 --- a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { flushPromises, postWindowMessage } from "../../autofill/spec/testing-utils"; - -describe("LP Suppress Import Download", () => { - const downloadAttribute = "file.csv"; - const hrefAttribute = "https://example.com/file.csv"; - const overridenHrefAttribute = "javascript:void(0)"; - let anchor: HTMLAnchorElement; - - beforeEach(() => { - jest.spyOn(Element.prototype, "appendChild"); - jest.spyOn(window, "addEventListener"); - - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-require-imports - require("./lp-suppress-import-download"); - - anchor = document.createElement("a"); - anchor.download = downloadAttribute; - anchor.href = hrefAttribute; - anchor.click = jest.fn(); - }); - - afterEach(() => { - jest.resetModules(); - jest.clearAllMocks(); - }); - - it("disables the automatic download anchor", () => { - document.body.appendChild(anchor); - - expect(anchor.href).toBe(overridenHrefAttribute); - expect(anchor.download).toBe(""); - }); - - it("triggers the CSVDownload when receiving a `triggerCsvDownload` window message", async () => { - window.document.createElement = jest.fn(() => anchor); - jest.spyOn(window, "removeEventListener"); - - document.body.appendChild(anchor); - - // Precondition - Ensure the anchor in the document has overridden href and download attributes - expect(anchor.href).toBe(overridenHrefAttribute); - expect(anchor.download).toBe(""); - - postWindowMessage({ command: "triggerCsvDownload" }); - await flushPromises(); - - expect(anchor.click).toHaveBeenCalled(); - expect(anchor.href).toEqual(hrefAttribute); - expect(anchor.download).toEqual(downloadAttribute); - expect(window.removeEventListener).toHaveBeenCalledWith("message", expect.any(Function)); - }); - - it("skips subsequent calls to trigger a CSVDownload", async () => { - window.document.createElement = jest.fn(() => anchor); - - document.body.appendChild(anchor); - - postWindowMessage({ command: "triggerCsvDownload" }); - await flushPromises(); - - postWindowMessage({ command: "triggerCsvDownload" }); - await flushPromises(); - - expect(anchor.click).toHaveBeenCalledTimes(1); - }); - - it("skips triggering the CSV download for window messages that do not have the correct command", () => { - document.body.appendChild(anchor); - - postWindowMessage({ command: "notTriggerCsvDownload" }); - - expect(anchor.click).not.toHaveBeenCalled(); - }); - - it("skips triggering the CSV download for window messages that do not have a data value", () => { - document.body.appendChild(anchor); - - postWindowMessage(null); - - expect(anchor.click).not.toHaveBeenCalled(); - }); -}); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download.ts b/apps/browser/src/tools/content/lp-suppress-import-download.ts deleted file mode 100644 index 1d5d449d199..00000000000 --- a/apps/browser/src/tools/content/lp-suppress-import-download.ts +++ /dev/null @@ -1,52 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -/** - * Handles intercepting the injection of the CSV download link, and ensures the - * download of the script is suppressed until the user opts to download the file. - * The download is triggered by a window message sent from the LpFilelessImporter - * content script. - */ -(function (globalContext) { - let csvDownload = ""; - let csvHref = ""; - let isCsvDownloadTriggered = false; - const defaultAppendChild = Element.prototype.appendChild; - Element.prototype.appendChild = function (newChild: Node) { - if (isAnchorElement(newChild) && newChild.download) { - csvDownload = newChild.download; - csvHref = newChild.href; - newChild.setAttribute("href", "javascript:void(0)"); - newChild.setAttribute("download", ""); - Element.prototype.appendChild = defaultAppendChild; - } - - return defaultAppendChild.call(this, newChild); - }; - - function isAnchorElement(node: Node): node is HTMLAnchorElement { - return node.nodeName.toLowerCase() === "a"; - } - - const handleWindowMessage = (event: MessageEvent) => { - const command = event.data?.command; - if ( - event.source !== globalContext || - command !== "triggerCsvDownload" || - isCsvDownloadTriggered - ) { - return; - } - - isCsvDownloadTriggered = true; - globalContext.removeEventListener("message", handleWindowMessage); - - const anchor = globalContext.document.createElement("a"); - anchor.setAttribute("href", csvHref); - anchor.setAttribute("download", csvDownload); - globalContext.document.body.appendChild(anchor); - anchor.click(); - globalContext.document.body.removeChild(anchor); - }; - - globalContext.addEventListener("message", handleWindowMessage); -})(window); diff --git a/apps/browser/src/tools/enums/fileless-import.enums.ts b/apps/browser/src/tools/enums/fileless-import.enums.ts deleted file mode 100644 index e20f4f15454..00000000000 --- a/apps/browser/src/tools/enums/fileless-import.enums.ts +++ /dev/null @@ -1,12 +0,0 @@ -const FilelessImportType = { - LP: "LP", -} as const; - -type FilelessImportTypeKeys = (typeof FilelessImportType)[keyof typeof FilelessImportType]; - -const FilelessImportPort = { - NotificationBar: "fileless-importer-notification-bar", - LpImporter: "lp-fileless-importer", -} as const; - -export { FilelessImportType, FilelessImportTypeKeys, FilelessImportPort }; diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 6ba74d7df43..bce41d64d1f 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -206,9 +206,7 @@ const mainConfig = { "overlay/list": "./src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts", "encrypt-worker": "../../libs/common/src/platform/services/cryptography/encrypt.worker.ts", - "content/lp-fileless-importer": "./src/tools/content/lp-fileless-importer.ts", "content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts", - "content/lp-suppress-import-download": "./src/tools/content/lp-suppress-import-download.ts", }, optimization: { minimize: ENV !== "development", @@ -312,8 +310,6 @@ if (manifestVersion == 2) { // Manifest V2 background pages can be run through the regular build pipeline. // Since it's a standard webpage. mainConfig.entry.background = "./src/platform/background.ts"; - mainConfig.entry["content/lp-suppress-import-download-script-append-mv2"] = - "./src/tools/content/lp-suppress-import-download-script-append.mv2.ts"; mainConfig.entry["content/fido2-page-script-append-mv2"] = "./src/autofill/fido2/content/fido2-page-script-append.mv2.ts"; mainConfig.entry["content/fido2-page-script-delay-append-mv2"] = diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index d008a09d66c..2ac4f507cdb 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -9,6 +9,7 @@ export enum FeatureFlag { DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", + IdpAutoSubmitLogin = "idp-auto-submit-login", InlineMenuFieldQualification = "inline-menu-field-qualification", InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", InlineMenuTotp = "inline-menu-totp", @@ -16,7 +17,6 @@ export enum FeatureFlag { NotificationRefresh = "notification-refresh", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", - BrowserFilelessImport = "browser-fileless-import", ItemShare = "item-share", GeneratorToolsModernization = "generator-tools-modernization", AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section", @@ -27,7 +27,6 @@ export enum FeatureFlag { TwoFactorComponentRefactor = "two-factor-component-refactor", ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", VaultBulkManagementAction = "vault-bulk-management-action", - IdpAutoSubmitLogin = "idp-auto-submit-login", UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", AccountDeprovisioning = "pm-10308-account-deprovisioning", SSHKeyVaultItem = "ssh-key-vault-item", @@ -66,6 +65,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, + [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.InlineMenuFieldQualification]: FALSE, [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, [FeatureFlag.InlineMenuTotp]: FALSE, @@ -73,7 +73,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NotificationRefresh]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, - [FeatureFlag.BrowserFilelessImport]: FALSE, [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.GeneratorToolsModernization]: FALSE, [FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE, @@ -84,7 +83,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.TwoFactorComponentRefactor]: FALSE, [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, [FeatureFlag.VaultBulkManagementAction]: FALSE, - [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, [FeatureFlag.AccountDeprovisioning]: FALSE, [FeatureFlag.SSHKeyVaultItem]: FALSE, From 87171289f028cec3a89188692cd5c6a374aeba70 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:16:52 -0500 Subject: [PATCH 238/270] Revert Opera to Mv2 build (#12921) --- .github/workflows/build-browser.yml | 6 +++++- apps/browser/package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 64cbaa0c7f1..974e07829c1 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -208,7 +208,11 @@ jobs: - name: "opera" npm_command: "dist:opera" archive_name: "dist-opera.zip" - artifact_name: "dist-opera-MV3" + artifact_name: "dist-opera" + - name: "opera-mv3" + npm_command: "dist:opera:mv3" + archive_name: "dist-opera.zip" + artifact_name: "DO-NOT-USE-FOR-PROD-dist-opera-MV3" steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/apps/browser/package.json b/apps/browser/package.json index 9ad1805362e..c37e7c24199 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -6,7 +6,7 @@ "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 webpack", "build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 webpack", "build:firefox": "cross-env BROWSER=firefox webpack", - "build:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 webpack", + "build:opera": "cross-env BROWSER=opera webpack", "build:safari": "cross-env BROWSER=safari webpack", "build:watch": "npm run build:watch:chrome", "build:watch:chrome": "npm run build:chrome -- --watch", From e5f83ff08605424b6656d7b379fd1d95f3f4d1b5 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 17 Jan 2025 16:42:31 +0100 Subject: [PATCH 239/270] [PM-17031] Create UI-common (#12831) Extract core functionality from `libs/angular` to allow teams to depend on `libs/ui-common` instead. Moves the following functionality to `ui-common`. - `I18nPipe`. `libs/angular` still has an old copy but `components` depends on the new variant from `ui-common`. - `safeProvider`, `SafeProvider` and `SafeInjectionToken`. `libs/angular`re-exports these to avoid needing to update all consumers. --- .github/CODEOWNERS | 1 + .github/whitelist-capital-letters.txt | 17 --- .github/workflows/lint.yml | 1 + .../popup/two-factor-auth-duo.component.ts | 4 - .../popup/two-factor-auth-email.component.ts | 4 - apps/browser/tsconfig.json | 15 +- .../src/auth/two-factor-auth.component.ts | 4 - apps/desktop/tsconfig.json | 9 +- .../integration-card.component.spec.ts | 2 +- .../integration-grid.component.spec.ts | 2 +- apps/web/tsconfig.json | 11 +- bitwarden_license/bit-common/tsconfig.json | 15 +- bitwarden_license/bit-web/tsconfig.json | 1 + libs/angular/src/platform/pipes/i18n.pipe.ts | 3 + .../src/platform/utils/safe-provider.ts | 138 +----------------- libs/angular/src/services/injection-tokens.ts | 15 +- .../src/services/jslib-services.module.ts | 2 +- libs/angular/tsconfig.json | 1 + libs/auth/tsconfig.json | 11 +- libs/common/tsconfig.json | 5 +- .../src/badge-list/badge-list.component.ts | 3 +- .../components/src/banner/banner.component.ts | 3 +- .../src/dialog/dialog/dialog.component.ts | 3 +- .../form-control/form-control.component.ts | 2 +- .../src/form-field/error-summary.component.ts | 2 +- .../src/form-field/form-field.component.ts | 3 +- .../multi-select/multi-select.component.ts | 2 +- .../src/navigation/nav-group.component.ts | 3 +- .../src/navigation/side-nav.component.ts | 3 +- .../src/radio-button/radio-group.component.ts | 3 +- .../components/src/search/search.component.ts | 2 +- libs/components/src/shared/shared.module.ts | 2 +- libs/components/tsconfig.json | 3 +- libs/importer/tsconfig.json | 1 + libs/key-management/tsconfig.json | 3 +- libs/shared/tsconfig.spec.json | 9 +- libs/tools/card/tsconfig.json | 3 +- .../vault-export-ui/tsconfig.json | 1 + libs/tools/generator/components/tsconfig.json | 3 +- libs/tools/send/send-ui/tsconfig.json | 3 +- libs/ui/README.md | 5 + libs/ui/common/package.json | 15 ++ libs/ui/common/src/di/index.ts | 2 + libs/ui/common/src/di/safe-injection-token.ts | 14 ++ libs/ui/common/src/di/safe-provider.ts | 138 ++++++++++++++++++ .../common/src/di}/safe-provider.type.spec.ts | 4 +- .../src/shared => ui/common/src}/i18n.pipe.ts | 8 +- libs/ui/common/src/index.ts | 2 + libs/ui/common/tsconfig.json | 10 ++ libs/vault/tsconfig.json | 1 + tsconfig.eslint.json | 17 ++- tsconfig.json | 13 +- 52 files changed, 300 insertions(+), 247 deletions(-) create mode 100644 libs/ui/README.md create mode 100644 libs/ui/common/package.json create mode 100644 libs/ui/common/src/di/index.ts create mode 100644 libs/ui/common/src/di/safe-injection-token.ts create mode 100644 libs/ui/common/src/di/safe-provider.ts rename libs/{angular/src/platform/utils => ui/common/src/di}/safe-provider.type.spec.ts (96%) rename libs/{components/src/shared => ui/common/src}/i18n.pipe.ts (77%) create mode 100644 libs/ui/common/src/index.ts create mode 100644 libs/ui/common/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cb36d87b9e1..d7cca18960a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -111,6 +111,7 @@ apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-dev @bit ## Component Library ## .storybook @bitwarden/team-design-system libs/components @bitwarden/team-design-system +libs/ui @bitwarden/team-design-system apps/browser/src/platform/popup/layout @bitwarden/team-design-system apps/browser/src/popup/app-routing.animations.ts @bitwarden/team-design-system apps/web/src/app/layouts @bitwarden/team-design-system diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 73d323851e5..653f6591c7f 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -3,26 +3,12 @@ ./apps/browser/src/safari/desktop/Assets.xcassets/AppIcon.appiconset ./apps/browser/src/safari/desktop/Base.lproj ./apps/browser/store/windows/Assets -./bitwarden_license/README.md ./libs/angular/src/directives/cipherListVirtualScroll.directive.ts -./libs/admin-console/README.md -./libs/auth/README.md -./libs/billing/README.md -./libs/common/src/tools/integration/README.md -./libs/platform/README.md -./libs/key-management/README.md -./libs/tools/README.md -./libs/tools/export/vault-export/README.md -./libs/tools/send/README.md -./libs/tools/card/README.md -./libs/vault/README.md -./README.md ./LICENSE_BITWARDEN.txt ./CONTRIBUTING.md ./LICENSE_GPL.txt ./LICENSE.txt ./apps/web/Dockerfile -./apps/web/README.md ./apps/desktop/resources/installerSidebar.bmp ./apps/desktop/resources/appx/SplashScreen.png ./apps/desktop/resources/appx/BadgeLogo.png @@ -30,10 +16,7 @@ ./apps/desktop/resources/appx/StoreLogo.png ./apps/desktop/resources/appx/Wide310x150Logo.png ./apps/desktop/resources/appx/Square44x44Logo.png -./apps/desktop/README.md ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt -./apps/cli/README.md -./apps/browser/README.md ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts ./apps/browser/src/models/browserComponentState.ts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 867de3844e7..5bc566202c6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,6 +34,7 @@ jobs: ! -path "*/.DS_Store" \ ! -path "*/*locales/*" \ ! -path "./.github/*" \ + ! -path "*/README.md" \ ! -path "*/Cargo.toml" \ ! -path "*/Cargo.lock" \ ! -path "./apps/desktop/macos/*" \ diff --git a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts index 53aedc7a5f3..687dc683929 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts @@ -27,9 +27,6 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field"; import { LinkModule } from "../../../../../libs/components/src/link"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports -import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; @@ -50,7 +47,6 @@ import { ZonedMessageListenerService } from "../../platform/browser/zoned-messag AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], }) export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent diff --git a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts index 723152adfab..7afe1eb889e 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts @@ -23,9 +23,6 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field"; import { LinkModule } from "../../../../../libs/components/src/link"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports -import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; @@ -46,7 +43,6 @@ import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], }) export class TwoFactorAuthEmailComponent extends TwoFactorAuthEmailBaseComponent implements OnInit { private dialogService = inject(DialogService); diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index c1ef1443acc..2326e68f55d 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -24,17 +24,18 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], + "@bitwarden/importer/core": ["../../libs/importer/src"], + "@bitwarden/importer/ui": ["../../libs/importer/src/components"], + "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], + "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/importer/core": ["../../libs/importer/src"], - "@bitwarden/importer/ui": ["../../libs/importer/src/components"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/vault": ["../../libs/vault/src"] }, "plugins": [ diff --git a/apps/desktop/src/auth/two-factor-auth.component.ts b/apps/desktop/src/auth/two-factor-auth.component.ts index 9e0898c39e2..d8b80c28df1 100644 --- a/apps/desktop/src/auth/two-factor-auth.component.ts +++ b/apps/desktop/src/auth/two-factor-auth.component.ts @@ -42,9 +42,6 @@ import { FormFieldModule } from "../../../../libs/components/src/form-field"; import { LinkModule } from "../../../../libs/components/src/link"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports -import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../libs/components/src/typography"; import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; @@ -73,6 +70,5 @@ import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; TwoFactorAuthDuoComponent, TwoFactorAuthWebAuthnComponent, ], - providers: [I18nPipe], }) export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent {} diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index da61ef22dd4..cbeb1034d6c 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -22,10 +22,6 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], @@ -34,6 +30,11 @@ "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/vault-export-core": [ + "../../libs/tools/export/vault-export/vault-export-core/src" + ], + "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"] }, "plugins": [ diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts index c8b6a290427..b9d54744761 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.spec.ts @@ -7,7 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { SharedModule } from "@bitwarden/components/src/shared"; -import { I18nPipe } from "@bitwarden/components/src/shared/i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; import { IntegrationCardComponent } from "./integration-card.component"; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts index c77ec455e00..ee124a41c0d 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.spec.ts @@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { ThemeTypes } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { SharedModule } from "@bitwarden/components/src/shared"; -import { I18nPipe } from "@bitwarden/components/src/shared/i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; import { IntegrationCardComponent } from "../integration-card/integration-card.component"; import { Integration } from "../models"; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 678db7c4af5..701808df132 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -7,8 +7,8 @@ "paths": { "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], + "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], @@ -18,10 +18,6 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/key-management": ["../../libs/key-management/src"], @@ -29,6 +25,11 @@ "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], + "@bitwarden/vault-export-core": [ + "../../libs/tools/export/vault-export/vault-export-core/src" + ], + "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["src/*"] } diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 7791840950b..ec1d3787f82 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -9,6 +9,7 @@ "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth": ["../../libs/auth/src"], "@bitwarden/billing": ["../../libs/billing/src"], + "@bitwarden/bit-common/*": ["../bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], @@ -16,18 +17,18 @@ "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../libs/key-management/src"], + "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], + "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": ["../../libs/tools/card/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], - "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/vault": ["../../libs/vault/src"], - "@bitwarden/web-vault/*": ["../../apps/web/src/*"], - "@bitwarden/bit-common/*": ["../bit-common/src/*"] + "@bitwarden/web-vault/*": ["../../apps/web/src/*"] } } } diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index c4304ec2bd9..13a6466b3b5 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -26,6 +26,7 @@ "@bitwarden/key-management": ["../../libs/key-management/src"], "@bitwarden/key-management/angular": ["../../libs/key-management/src/angular"], "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/vault": ["../../libs/vault/src"], diff --git a/libs/angular/src/platform/pipes/i18n.pipe.ts b/libs/angular/src/platform/pipes/i18n.pipe.ts index 1f92bbb19a4..a6fdbc78255 100644 --- a/libs/angular/src/platform/pipes/i18n.pipe.ts +++ b/libs/angular/src/platform/pipes/i18n.pipe.ts @@ -2,6 +2,9 @@ import { Pipe, PipeTransform } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +/** + * @deprecated: Please use the I18nPipe from @bitwarden/ui-common + */ @Pipe({ name: "i18n", }) diff --git a/libs/angular/src/platform/utils/safe-provider.ts b/libs/angular/src/platform/utils/safe-provider.ts index e7547f9b828..82b5affcc22 100644 --- a/libs/angular/src/platform/utils/safe-provider.ts +++ b/libs/angular/src/platform/utils/safe-provider.ts @@ -1,138 +1,4 @@ -import { Provider } from "@angular/core"; -import { Constructor, Opaque } from "type-fest"; - -import { SafeInjectionToken } from "../../services/injection-tokens"; - /** - * The return type of the {@link safeProvider} helper function. - * Used to distinguish a type safe provider definition from a non-type safe provider definition. + * @deprecated: Please use the SafeProvider & safeProvider from @bitwarden/ui-common */ -export type SafeProvider = Opaque; - -// TODO: type-fest also provides a type like this when we upgrade >= 3.7.0 -type AbstractConstructor = abstract new (...args: any) => T; - -type MapParametersToDeps = { - [K in keyof T]: AbstractConstructor | SafeInjectionToken; -}; - -type SafeInjectionTokenType = T extends SafeInjectionToken ? J : never; - -/** - * Gets the instance type from a constructor, abstract constructor, or SafeInjectionToken - */ -type ProviderInstanceType = - T extends SafeInjectionToken - ? InstanceType> - : T extends Constructor | AbstractConstructor - ? InstanceType - : never; - -/** - * Represents a dependency provided with the useClass option. - */ -type SafeClassProvider< - A extends AbstractConstructor | SafeInjectionToken, - I extends Constructor>, - D extends MapParametersToDeps>, -> = { - provide: A; - useClass: I; - deps: D; -}; - -/** - * Represents a dependency provided with the useValue option. - */ -type SafeValueProvider, V extends SafeInjectionTokenType> = { - provide: A; - useValue: V; -}; - -/** - * Represents a dependency provided with the useFactory option. - */ -type SafeFactoryProvider< - A extends AbstractConstructor | SafeInjectionToken, - I extends (...args: any) => ProviderInstanceType, - D extends MapParametersToDeps>, -> = { - provide: A; - useFactory: I; - deps: D; - multi?: boolean; -}; - -/** - * Represents a dependency provided with the useExisting option. - */ -type SafeExistingProvider< - A extends Constructor | AbstractConstructor | SafeInjectionToken, - I extends Constructor> | AbstractConstructor>, -> = { - provide: A; - useExisting: I; -}; - -/** - * Represents a dependency where there is no abstract token, the token is the implementation - */ -type SafeConcreteProvider< - I extends Constructor, - D extends MapParametersToDeps>, -> = { - provide: I; - deps: D; -}; - -/** - * If useAngularDecorators: true is specified, do not require a deps array. - * This is a manual override for where @Injectable decorators are used - */ -type UseAngularDecorators = Omit & { - useAngularDecorators: true; -}; - -/** - * Represents a type with a deps array that may optionally be overridden with useAngularDecorators - */ -type AllowAngularDecorators = T | UseAngularDecorators; - -/** - * A factory function that creates a provider for the ngModule providers array. - * This (almost) guarantees type safety for your provider definition. It does nothing at runtime. - * Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator, - * however this cannot be enforced by the type system and will not cause an error if the decorator is not used. - * @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] }) - * @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.) - * @returns The exact same object without modification (pass-through). - */ -export const safeProvider = < - // types for useClass - AClass extends AbstractConstructor | SafeInjectionToken, - IClass extends Constructor>, - DClass extends MapParametersToDeps>, - // types for useValue - AValue extends SafeInjectionToken, - VValue extends SafeInjectionTokenType, - // types for useFactory - AFactory extends AbstractConstructor | SafeInjectionToken, - IFactory extends (...args: any) => ProviderInstanceType, - DFactory extends MapParametersToDeps>, - // types for useExisting - AExisting extends Constructor | AbstractConstructor | SafeInjectionToken, - IExisting extends - | Constructor> - | AbstractConstructor>, - // types for no token - IConcrete extends Constructor, - DConcrete extends MapParametersToDeps>, ->( - provider: - | AllowAngularDecorators> - | SafeValueProvider - | AllowAngularDecorators> - | SafeExistingProvider - | AllowAngularDecorators> - | Constructor, -): SafeProvider => provider as SafeProvider; +export { SafeProvider, safeProvider } from "@bitwarden/ui-common"; diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 3842c3250e1..2c740d5bb42 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,6 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; @@ -14,17 +13,9 @@ import { Theme } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message } from "@bitwarden/common/platform/messaging"; import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; - -declare const tag: unique symbol; -/** - * A (more) typesafe version of InjectionToken which will more strictly enforce the generic type parameter. - * @remarks The default angular implementation does not use the generic type to define the structure of the object, - * so the structural type system will not complain about a mismatch in the type parameter. - * This is solved by assigning T to an arbitrary private property. - */ -export class SafeInjectionToken extends InjectionToken { - private readonly [tag]: T; -} +import { SafeInjectionToken } from "@bitwarden/ui-common"; +// Re-export the SafeInjectionToken from ui-common +export { SafeInjectionToken } from "@bitwarden/ui-common"; export const WINDOW = new SafeInjectionToken("WINDOW"); export const OBSERVABLE_MEMORY_STORAGE = new SafeInjectionToken< diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 803808612cf..c1d25407c0e 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -293,6 +293,7 @@ import { UserAsymmetricKeysRegenerationApiService, DefaultUserAsymmetricKeysRegenerationApiService, } from "@bitwarden/key-management"; +import { SafeInjectionToken } from "@bitwarden/ui-common"; import { PasswordRepromptService } from "@bitwarden/vault"; import { VaultExportService, @@ -323,7 +324,6 @@ import { MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE, - SafeInjectionToken, SECURE_STORAGE, STATE_FACTORY, SUPPORTS_SECURE_STORAGE, diff --git a/libs/angular/tsconfig.json b/libs/angular/tsconfig.json index 6c510f81492..b638410a6a8 100644 --- a/libs/angular/tsconfig.json +++ b/libs/angular/tsconfig.json @@ -16,6 +16,7 @@ "@bitwarden/importer/core": ["../importer/src"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], "@bitwarden/vault": ["../vault/src"] } diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json index 9be942d38de..8d08522ffce 100644 --- a/libs/auth/tsconfig.json +++ b/libs/auth/tsconfig.json @@ -4,17 +4,18 @@ "resolveJsonModule": true, "paths": { "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/auth/angular": ["../auth/src/angular"], "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"], "@bitwarden/generator-core": ["../tools/generator/core/src"], "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"] + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"] } }, "include": ["src", "spec"], diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index c28b60e28f8..2d1379f9c5f 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -6,9 +6,12 @@ "@bitwarden/auth/common": ["../auth/src/common"], // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved "@bitwarden/common/*": ["../common/src/*"], + // TODO: Remove once billing stops depending on components "@bitwarden/components": ["../components/src"], "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"] + "@bitwarden/platform": ["../platform/src"], + // TODO: Remove once billing stops depending on components + "@bitwarden/ui-common": ["../ui/common/src"] } }, "include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"], diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index ac8cb3281ab..7d152761ed0 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -3,8 +3,9 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BadgeModule, BadgeVariant } from "../badge"; -import { I18nPipe } from "../shared/i18n.pipe"; @Component({ selector: "bit-badge-list", diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index d3f64329978..a7b710d6a74 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -3,8 +3,9 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { IconButtonModule } from "../icon-button"; -import { I18nPipe } from "../shared/i18n.pipe"; type BannerTypes = "premium" | "info" | "warning" | "danger"; diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index ed47201805a..e9e3e898257 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -4,8 +4,9 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { CommonModule } from "@angular/common"; import { Component, HostBinding, Input } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitIconButtonComponent } from "../../icon-button/icon-button.component"; -import { I18nPipe } from "../../shared/i18n.pipe"; import { TypographyDirective } from "../../typography/typography.directive"; import { fadeIn } from "../animations"; import { DialogCloseDirective } from "../directives/dialog-close.directive"; diff --git a/libs/components/src/form-control/form-control.component.ts b/libs/components/src/form-control/form-control.component.ts index 9b87c44157a..d22d49ac03a 100644 --- a/libs/components/src/form-control/form-control.component.ts +++ b/libs/components/src/form-control/form-control.component.ts @@ -5,8 +5,8 @@ import { NgClass, NgIf } from "@angular/common"; import { Component, ContentChild, HostBinding, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nPipe } from "@bitwarden/ui-common"; -import { I18nPipe } from "../shared/i18n.pipe"; import { TypographyDirective } from "../typography/typography.directive"; import { BitFormControlAbstraction } from "./form-control.abstraction"; diff --git a/libs/components/src/form-field/error-summary.component.ts b/libs/components/src/form-field/error-summary.component.ts index beed32a88ac..f9325d8f82a 100644 --- a/libs/components/src/form-field/error-summary.component.ts +++ b/libs/components/src/form-field/error-summary.component.ts @@ -4,7 +4,7 @@ import { NgIf } from "@angular/common"; import { Component, Input } from "@angular/core"; import { AbstractControl, UntypedFormGroup } from "@angular/forms"; -import { I18nPipe } from "../shared/i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "bit-error-summary", diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 9f41c6cf6ac..4f7c2a67483 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -14,10 +14,11 @@ import { signal, } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitHintComponent } from "../form-control/hint.component"; import { BitLabel } from "../form-control/label.component"; import { inputBorderClasses } from "../input/input.directive"; -import { I18nPipe } from "../shared/i18n.pipe"; import { BitErrorComponent } from "./error.component"; import { BitFormFieldControl } from "./form-field-control"; diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index 53e51bfe2f9..71b00404cfb 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -24,10 +24,10 @@ import { import { NgSelectComponent, NgSelectModule } from "@ng-select/ng-select"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nPipe } from "@bitwarden/ui-common"; import { BadgeModule } from "../badge"; import { BitFormFieldControl } from "../form-field/form-field-control"; -import { I18nPipe } from "../shared/i18n.pipe"; import { SelectItemView } from "./models/select-item-view"; diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index d615bfe0582..37244f37c8d 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -12,8 +12,9 @@ import { SkipSelf, } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { IconButtonModule } from "../icon-button"; -import { I18nPipe } from "../shared/i18n.pipe"; import { NavBaseComponent } from "./nav-base.component"; import { NavGroupAbstraction, NavItemComponent } from "./nav-item.component"; diff --git a/libs/components/src/navigation/side-nav.component.ts b/libs/components/src/navigation/side-nav.component.ts index c86a517100f..e8e4f131d6d 100644 --- a/libs/components/src/navigation/side-nav.component.ts +++ b/libs/components/src/navigation/side-nav.component.ts @@ -4,8 +4,9 @@ import { CdkTrapFocus } from "@angular/cdk/a11y"; import { CommonModule } from "@angular/common"; import { Component, ElementRef, Input, ViewChild } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitIconButtonComponent } from "../icon-button/icon-button.component"; -import { I18nPipe } from "../shared/i18n.pipe"; import { NavDividerComponent } from "./nav-divider.component"; import { SideNavService } from "./side-nav.service"; diff --git a/libs/components/src/radio-button/radio-group.component.ts b/libs/components/src/radio-button/radio-group.component.ts index b9e48f46445..4ab626f7964 100644 --- a/libs/components/src/radio-button/radio-group.component.ts +++ b/libs/components/src/radio-button/radio-group.component.ts @@ -4,8 +4,9 @@ import { NgIf, NgTemplateOutlet } from "@angular/common"; import { Component, ContentChild, HostBinding, Input, Optional, Self } from "@angular/core"; import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { BitLabel } from "../form-control/label.component"; -import { I18nPipe } from "../shared/i18n.pipe"; let nextId = 0; diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 9a811ce6777..7f1bd781e9d 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -9,10 +9,10 @@ import { } from "@angular/forms"; import { isBrowserSafariApi } from "@bitwarden/platform"; +import { I18nPipe } from "@bitwarden/ui-common"; import { InputModule } from "../input/input.module"; import { FocusableElement } from "../shared/focusable-element"; -import { I18nPipe } from "../shared/i18n.pipe"; let nextId = 0; diff --git a/libs/components/src/shared/shared.module.ts b/libs/components/src/shared/shared.module.ts index 253b049f8fe..99d052c3350 100644 --- a/libs/components/src/shared/shared.module.ts +++ b/libs/components/src/shared/shared.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { I18nPipe } from "./i18n.pipe"; +import { I18nPipe } from "@bitwarden/ui-common"; @NgModule({ imports: [CommonModule, I18nPipe], diff --git a/libs/components/tsconfig.json b/libs/components/tsconfig.json index 71eef15fac4..abd5830d425 100644 --- a/libs/components/tsconfig.json +++ b/libs/components/tsconfig.json @@ -20,7 +20,8 @@ "lib": ["es2020", "dom"], "paths": { "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/platform": ["../platform/src"] + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"] }, "plugins": [ { diff --git a/libs/importer/tsconfig.json b/libs/importer/tsconfig.json index 2235cccb5c7..09eb33e2884 100644 --- a/libs/importer/tsconfig.json +++ b/libs/importer/tsconfig.json @@ -13,6 +13,7 @@ "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"] } }, diff --git a/libs/key-management/tsconfig.json b/libs/key-management/tsconfig.json index 8279f14c786..e1e618314e8 100644 --- a/libs/key-management/tsconfig.json +++ b/libs/key-management/tsconfig.json @@ -13,7 +13,8 @@ "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/platform": ["../platform/src"] + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"] } }, "include": ["src", "spec"], diff --git a/libs/shared/tsconfig.spec.json b/libs/shared/tsconfig.spec.json index 2366507918e..92c957c2975 100644 --- a/libs/shared/tsconfig.spec.json +++ b/libs/shared/tsconfig.spec.json @@ -5,8 +5,8 @@ "paths": { "@bitwarden/admin-console/common": ["../admin-console/src/common"], "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/billing": ["../billing/src"], "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], @@ -15,16 +15,17 @@ "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": ["../tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["../importer/src"], "@bitwarden/importer/ui": ["../importer/src/components"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/key-management/angular": ["../key-management/src/angular"], + "@bitwarden/node/*": ["../node/src/*"], "@bitwarden/platform": ["../platform/src"], "@bitwarden/send-ui": ["../tools/send/send-ui/src"], "@bitwarden/tools-card": ["../tools/card/src"], - "@bitwarden/node/*": ["../node/src/*"], + "@bitwarden/ui-common": ["../ui/common/src"], + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": ["../tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["../vault/src"] } } diff --git a/libs/tools/card/tsconfig.json b/libs/tools/card/tsconfig.json index 9c910521cc6..050a1748b7b 100644 --- a/libs/tools/card/tsconfig.json +++ b/libs/tools/card/tsconfig.json @@ -8,7 +8,8 @@ "@bitwarden/common/*": ["../../common/src/*"], "@bitwarden/components": ["../../components/src"], "@bitwarden/key-management": ["../../key-management/src"], - "@bitwarden/platform": ["../../platform/src"] + "@bitwarden/platform": ["../../platform/src"], + "@bitwarden/ui-common": ["../../ui/common/src"] } }, "include": ["src"], diff --git a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json index 8c8a04d4b62..1732817986e 100644 --- a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json @@ -14,6 +14,7 @@ "@bitwarden/generator-navigation": ["../../../../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../../../../key-management/src"], "@bitwarden/platform": ["../../../../platform/src"], + "@bitwarden/ui-common": ["../../../../ui/common/src"], "@bitwarden/vault-export-core": [ "../../../../tools/export/vault-export/vault-export-core/src" ] diff --git a/libs/tools/generator/components/tsconfig.json b/libs/tools/generator/components/tsconfig.json index 76b060334f6..e0e4da268da 100644 --- a/libs/tools/generator/components/tsconfig.json +++ b/libs/tools/generator/components/tsconfig.json @@ -10,7 +10,8 @@ "@bitwarden/generator-core": ["../../../tools/generator/core/src"], "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], "@bitwarden/key-management": ["../../../key-management/src"], - "@bitwarden/platform": ["../../../platform/src"] + "@bitwarden/platform": ["../../../platform/src"], + "@bitwarden/ui-common": ["../../../ui/common/src"] } }, "include": ["src"], diff --git a/libs/tools/send/send-ui/tsconfig.json b/libs/tools/send/send-ui/tsconfig.json index 671154e0a04..e6d6680ad40 100644 --- a/libs/tools/send/send-ui/tsconfig.json +++ b/libs/tools/send/send-ui/tsconfig.json @@ -13,7 +13,8 @@ "@bitwarden/generator-legacy": ["../../../tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["../../../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../../../key-management/src"], - "@bitwarden/platform": ["../../../platform/src"] + "@bitwarden/platform": ["../../../platform/src"], + "@bitwarden/ui-common": ["../../../ui/common/src"] } }, "include": ["src"], diff --git a/libs/ui/README.md b/libs/ui/README.md new file mode 100644 index 00000000000..e245aac71cb --- /dev/null +++ b/libs/ui/README.md @@ -0,0 +1,5 @@ +# UI Foundation + +Core UI libraries maintained by the UI Foundation team. + +- _ui-common_: Low-level utilities for Angular applications. diff --git a/libs/ui/common/package.json b/libs/ui/common/package.json new file mode 100644 index 00000000000..f1b03a3ebd0 --- /dev/null +++ b/libs/ui/common/package.json @@ -0,0 +1,15 @@ +{ + "name": "@bitwarden/ui-common", + "version": "0.0.0", + "description": "Low-level utilities for Angular applications", + "keywords": [ + "bitwarden" + ], + "author": "Bitwarden Inc.", + "homepage": "https://bitwarden.com", + "repository": { + "type": "git", + "url": "https://github.com/bitwarden/clients" + }, + "license": "GPL-3.0" +} diff --git a/libs/ui/common/src/di/index.ts b/libs/ui/common/src/di/index.ts new file mode 100644 index 00000000000..7b0705f1364 --- /dev/null +++ b/libs/ui/common/src/di/index.ts @@ -0,0 +1,2 @@ +export * from "./safe-injection-token"; +export * from "./safe-provider"; diff --git a/libs/ui/common/src/di/safe-injection-token.ts b/libs/ui/common/src/di/safe-injection-token.ts new file mode 100644 index 00000000000..aad081e1c76 --- /dev/null +++ b/libs/ui/common/src/di/safe-injection-token.ts @@ -0,0 +1,14 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { InjectionToken } from "@angular/core"; + +declare const tag: unique symbol; +/** + * A (more) typesafe version of InjectionToken which will more strictly enforce the generic type parameter. + * @remarks The default angular implementation does not use the generic type to define the structure of the object, + * so the structural type system will not complain about a mismatch in the type parameter. + * This is solved by assigning T to an arbitrary private property. + */ +export class SafeInjectionToken extends InjectionToken { + private readonly [tag]: T; +} diff --git a/libs/ui/common/src/di/safe-provider.ts b/libs/ui/common/src/di/safe-provider.ts new file mode 100644 index 00000000000..002aa69a500 --- /dev/null +++ b/libs/ui/common/src/di/safe-provider.ts @@ -0,0 +1,138 @@ +import { Provider } from "@angular/core"; +import { Constructor, Opaque } from "type-fest"; + +import { SafeInjectionToken } from "./safe-injection-token"; + +/** + * The return type of the {@link safeProvider} helper function. + * Used to distinguish a type safe provider definition from a non-type safe provider definition. + */ +export type SafeProvider = Opaque; + +// TODO: type-fest also provides a type like this when we upgrade >= 3.7.0 +type AbstractConstructor = abstract new (...args: any) => T; + +type MapParametersToDeps = { + [K in keyof T]: AbstractConstructor | SafeInjectionToken; +}; + +type SafeInjectionTokenType = T extends SafeInjectionToken ? J : never; + +/** + * Gets the instance type from a constructor, abstract constructor, or SafeInjectionToken + */ +type ProviderInstanceType = + T extends SafeInjectionToken + ? InstanceType> + : T extends Constructor | AbstractConstructor + ? InstanceType + : never; + +/** + * Represents a dependency provided with the useClass option. + */ +type SafeClassProvider< + A extends AbstractConstructor | SafeInjectionToken, + I extends Constructor>, + D extends MapParametersToDeps>, +> = { + provide: A; + useClass: I; + deps: D; +}; + +/** + * Represents a dependency provided with the useValue option. + */ +type SafeValueProvider, V extends SafeInjectionTokenType> = { + provide: A; + useValue: V; +}; + +/** + * Represents a dependency provided with the useFactory option. + */ +type SafeFactoryProvider< + A extends AbstractConstructor | SafeInjectionToken, + I extends (...args: any) => ProviderInstanceType, + D extends MapParametersToDeps>, +> = { + provide: A; + useFactory: I; + deps: D; + multi?: boolean; +}; + +/** + * Represents a dependency provided with the useExisting option. + */ +type SafeExistingProvider< + A extends Constructor | AbstractConstructor | SafeInjectionToken, + I extends Constructor> | AbstractConstructor>, +> = { + provide: A; + useExisting: I; +}; + +/** + * Represents a dependency where there is no abstract token, the token is the implementation + */ +type SafeConcreteProvider< + I extends Constructor, + D extends MapParametersToDeps>, +> = { + provide: I; + deps: D; +}; + +/** + * If useAngularDecorators: true is specified, do not require a deps array. + * This is a manual override for where @Injectable decorators are used + */ +type UseAngularDecorators = Omit & { + useAngularDecorators: true; +}; + +/** + * Represents a type with a deps array that may optionally be overridden with useAngularDecorators + */ +type AllowAngularDecorators = T | UseAngularDecorators; + +/** + * A factory function that creates a provider for the ngModule providers array. + * This (almost) guarantees type safety for your provider definition. It does nothing at runtime. + * Warning: the useAngularDecorators option provides an override where your class uses the Injectable decorator, + * however this cannot be enforced by the type system and will not cause an error if the decorator is not used. + * @example safeProvider({ provide: MyService, useClass: DefaultMyService, deps: [AnotherService] }) + * @param provider Your provider object in the usual shape (e.g. using useClass, useValue, useFactory, etc.) + * @returns The exact same object without modification (pass-through). + */ +export const safeProvider = < + // types for useClass + AClass extends AbstractConstructor | SafeInjectionToken, + IClass extends Constructor>, + DClass extends MapParametersToDeps>, + // types for useValue + AValue extends SafeInjectionToken, + VValue extends SafeInjectionTokenType, + // types for useFactory + AFactory extends AbstractConstructor | SafeInjectionToken, + IFactory extends (...args: any) => ProviderInstanceType, + DFactory extends MapParametersToDeps>, + // types for useExisting + AExisting extends Constructor | AbstractConstructor | SafeInjectionToken, + IExisting extends + | Constructor> + | AbstractConstructor>, + // types for no token + IConcrete extends Constructor, + DConcrete extends MapParametersToDeps>, +>( + provider: + | AllowAngularDecorators> + | SafeValueProvider + | AllowAngularDecorators> + | SafeExistingProvider + | AllowAngularDecorators> + | Constructor, +): SafeProvider => provider as SafeProvider; diff --git a/libs/angular/src/platform/utils/safe-provider.type.spec.ts b/libs/ui/common/src/di/safe-provider.type.spec.ts similarity index 96% rename from libs/angular/src/platform/utils/safe-provider.type.spec.ts rename to libs/ui/common/src/di/safe-provider.type.spec.ts index 6fe6d0d0b6c..afc7071af1e 100644 --- a/libs/angular/src/platform/utils/safe-provider.type.spec.ts +++ b/libs/ui/common/src/di/safe-provider.type.spec.ts @@ -11,7 +11,7 @@ class FooFactory { } abstract class FooService { - createFoo: (str: string) => string; + abstract createFoo(str: string): string; } class DefaultFooService implements FooService { @@ -29,7 +29,7 @@ class BarFactory { } abstract class BarService { - createBar: (num: number) => number; + abstract createBar(num: number): number; } class DefaultBarService implements BarService { diff --git a/libs/components/src/shared/i18n.pipe.ts b/libs/ui/common/src/i18n.pipe.ts similarity index 77% rename from libs/components/src/shared/i18n.pipe.ts rename to libs/ui/common/src/i18n.pipe.ts index 91bf0b3198d..fdcfec0ceac 100644 --- a/libs/components/src/shared/i18n.pipe.ts +++ b/libs/ui/common/src/i18n.pipe.ts @@ -3,7 +3,13 @@ import { Pipe, PipeTransform } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; /** - * Temporarily duplicate this pipe + * Localizes the specified string. + * + * @example + * {{ 'key' | i18n }} + * + * @example + * {{ 'key' | i18n: 'param1' }} */ @Pipe({ name: "i18n", diff --git a/libs/ui/common/src/index.ts b/libs/ui/common/src/index.ts new file mode 100644 index 00000000000..97e55108116 --- /dev/null +++ b/libs/ui/common/src/index.ts @@ -0,0 +1,2 @@ +export * from "./di"; +export * from "./i18n.pipe"; diff --git a/libs/ui/common/tsconfig.json b/libs/ui/common/tsconfig.json new file mode 100644 index 00000000000..31062d41a1c --- /dev/null +++ b/libs/ui/common/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/common/*": ["../../common/src/*"] + } + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/libs/vault/tsconfig.json b/libs/vault/tsconfig.json index 8318212e81d..e1515183f22 100644 --- a/libs/vault/tsconfig.json +++ b/libs/vault/tsconfig.json @@ -15,6 +15,7 @@ "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"], + "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/vault": ["../vault/src"] } }, diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index a69452389f5..980d7832ace 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -20,6 +20,7 @@ "@bitwarden/angular/*": ["./libs/angular/src/*"], "@bitwarden/auth": ["./libs/auth/src"], "@bitwarden/billing": ["./libs/billing/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], @@ -27,18 +28,18 @@ "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [".libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": [".libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["./libs/importer/src"], "@bitwarden/importer/ui": ["./libs/importer/src/components"], - "@bitwarden/send-ui": [".libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": [".libs/tools/card/src"], - "@bitwarden/platform": ["./libs/platform/src"], - "@bitwarden/node/*": ["./libs/node/src/*"], - "@bitwarden/vault": ["./libs/vault/src"], "@bitwarden/key-management": ["./libs/key-management/src"], "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], - "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] + "@bitwarden/node/*": ["./libs/node/src/*"], + "@bitwarden/platform": ["./libs/platform/src"], + "@bitwarden/send-ui": [".libs/tools/send/send-ui/src"], + "@bitwarden/tools-card": [".libs/tools/card/src"], + "@bitwarden/ui-common": ["./libs/ui/common/src"], + "@bitwarden/vault-export-core": [".libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": [".libs/tools/export/vault-export/vault-export-ui/src"], + "@bitwarden/vault": ["./libs/vault/src"] }, "plugins": [ { diff --git a/tsconfig.json b/tsconfig.json index 91b4ee7dd6b..efa2ff70e1b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,9 +18,10 @@ "paths": { "@bitwarden/admin-console/common": ["./libs/admin-console/src/common"], "@bitwarden/angular/*": ["./libs/angular/src/*"], - "@bitwarden/auth/common": ["./libs/auth/src/common"], "@bitwarden/auth/angular": ["./libs/auth/src/angular"], + "@bitwarden/auth/common": ["./libs/auth/src/common"], "@bitwarden/billing": ["./libs/billing/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], @@ -28,19 +29,19 @@ "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/importer/core": ["./libs/importer/src"], "@bitwarden/importer/ui": ["./libs/importer/src/components"], "@bitwarden/key-management": ["./libs/key-management/src"], "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], + "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], "@bitwarden/tools-card": ["./libs/tools/card/src"], - "@bitwarden/node/*": ["./libs/node/src/*"], - "@bitwarden/web-vault/*": ["./apps/web/src/*"], + "@bitwarden/ui-common": ["./libs/ui/common/src"], + "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], "@bitwarden/vault": ["./libs/vault/src"], - "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] + "@bitwarden/web-vault/*": ["./apps/web/src/*"] }, "plugins": [ { From 23227f50646bcf6e9e8e3579d391895c0a32e485 Mon Sep 17 00:00:00 2001 From: Danielle Flinn <43477473+danielleflinn@users.noreply.github.com> Date: Fri, 17 Jan 2025 07:50:20 -0800 Subject: [PATCH 240/270] [CL-256] Update Figma links in Storybook docs (#12901) * Update Figma links updated existing Figma links to point to the new file and added Figma links to components missing them * Added last missing Figma links --- .../src/platform/popup/layout/popup-layout.stories.ts | 4 ++++ libs/components/src/avatar/avatar.stories.ts | 2 +- libs/components/src/badge-list/badge-list.stories.ts | 2 +- libs/components/src/badge/badge.stories.ts | 2 +- libs/components/src/banner/banner.stories.ts | 2 +- libs/components/src/breadcrumbs/breadcrumbs.stories.ts | 6 ++++++ libs/components/src/button/button.stories.ts | 2 +- libs/components/src/callout/callout.stories.ts | 2 +- libs/components/src/card/card.stories.ts | 6 ++++++ libs/components/src/checkbox/checkbox.stories.ts | 2 +- libs/components/src/chip-select/chip-select.stories.ts | 6 ++++++ .../components/src/color-password/color-password.stories.ts | 2 +- libs/components/src/dialog/dialog.service.stories.ts | 2 +- libs/components/src/dialog/dialog/dialog.stories.ts | 2 +- .../simple-configurable-dialog.service.stories.ts | 2 +- .../dialog/simple-dialog/simple-dialog.service.stories.ts | 2 +- .../src/dialog/simple-dialog/simple-dialog.stories.ts | 2 +- libs/components/src/disclosure/disclosure.stories.ts | 6 ++++++ libs/components/src/form-field/bit-validators.stories.ts | 2 +- libs/components/src/form-field/error-summary.stories.ts | 2 +- libs/components/src/form-field/form-field.stories.ts | 2 +- libs/components/src/form-field/multi-select.stories.ts | 2 +- .../src/form-field/password-input-toggle.stories.ts | 2 +- libs/components/src/form/form.stories.ts | 2 +- libs/components/src/icon-button/icon-button.stories.ts | 2 +- libs/components/src/icon/icon.stories.ts | 6 ++++++ libs/components/src/item/item.stories.ts | 6 ++++++ libs/components/src/layout/layout.stories.ts | 4 ++++ libs/components/src/link/link.stories.ts | 2 +- libs/components/src/menu/menu.stories.ts | 2 +- libs/components/src/navigation/nav-group.stories.ts | 2 +- libs/components/src/navigation/nav-item.stories.ts | 2 +- libs/components/src/no-items/no-items.stories.ts | 6 ++++++ libs/components/src/popover/popover.stories.ts | 2 +- libs/components/src/progress/progress.stories.ts | 2 +- libs/components/src/radio-button/radio-button.stories.ts | 2 +- libs/components/src/section/section.stories.ts | 6 ++++++ libs/components/src/select/select.stories.ts | 2 +- libs/components/src/table/table.stories.ts | 2 +- libs/components/src/tabs/tabs.stories.ts | 2 +- libs/components/src/toast/toast.stories.ts | 2 +- libs/components/src/toggle-group/toggle-group.stories.ts | 2 +- 42 files changed, 88 insertions(+), 32 deletions(-) diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index 6a2d8162c7f..43693ca223b 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -306,6 +306,10 @@ export default { // Disable tests while we troubleshoot their flaky-ness disableSnapshot: true, }, + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-38889&t=k6OTDDPZOTtypRqo-11", + }, }, decorators: [ moduleMetadata({ diff --git a/libs/components/src/avatar/avatar.stories.ts b/libs/components/src/avatar/avatar.stories.ts index d3a00fbe344..19a6f86d89c 100644 --- a/libs/components/src/avatar/avatar.stories.ts +++ b/libs/components/src/avatar/avatar.stories.ts @@ -13,7 +13,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16994", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26525&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/badge-list/badge-list.stories.ts b/libs/components/src/badge-list/badge-list.stories.ts index ede005f6fd6..f69ecde8377 100644 --- a/libs/components/src/badge-list/badge-list.stories.ts +++ b/libs/components/src/badge-list/badge-list.stories.ts @@ -33,7 +33,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A16956", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26440&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/badge/badge.stories.ts b/libs/components/src/badge/badge.stories.ts index 5d697f8ad8f..bff9eec6163 100644 --- a/libs/components/src/badge/badge.stories.ts +++ b/libs/components/src/badge/badge.stories.ts @@ -18,7 +18,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16956", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26440&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/banner/banner.stories.ts b/libs/components/src/banner/banner.stories.ts index c75d8b7034a..105d30bc04a 100644 --- a/libs/components/src/banner/banner.stories.ts +++ b/libs/components/src/banner/banner.stories.ts @@ -30,7 +30,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=2070%3A17207", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26720&t=b5tDKylm5sWm2yKo-4", }, }, args: { diff --git a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts index 9c8ccbccd3f..eb75a70ad75 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.stories.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.stories.ts @@ -35,6 +35,12 @@ export default { ], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-26962&t=b5tDKylm5sWm2yKo-4", + }, + }, args: { items: [], show: 3, diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 3654442801c..469c2d1b51b 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -13,7 +13,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=5115%3A26950", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-28224&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/callout/callout.stories.ts b/libs/components/src/callout/callout.stories.ts index cb51e96e8da..3101d4316f1 100644 --- a/libs/components/src/callout/callout.stories.ts +++ b/libs/components/src/callout/callout.stories.ts @@ -30,7 +30,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17484", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-28300&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/card/card.stories.ts b/libs/components/src/card/card.stories.ts index b33f5f4a198..3482eedfd54 100644 --- a/libs/components/src/card/card.stories.ts +++ b/libs/components/src/card/card.stories.ts @@ -34,6 +34,12 @@ export default { (story) => `
    ${story}
    `, ), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-28355&t=b5tDKylm5sWm2yKo-4", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index f3e5a5cb3f7..908085bb6df 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -83,7 +83,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=3930%3A16850&t=xXPx6GJYsJfuMQPE-4", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-35837&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/chip-select/chip-select.stories.ts b/libs/components/src/chip-select/chip-select.stories.ts index 598fa5b80be..e9b78235ccb 100644 --- a/libs/components/src/chip-select/chip-select.stories.ts +++ b/libs/components/src/chip-select/chip-select.stories.ts @@ -30,6 +30,12 @@ export default { ], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-29548&t=b5tDKylm5sWm2yKo-4", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/color-password/color-password.stories.ts b/libs/components/src/color-password/color-password.stories.ts index 07418cad721..bb835d97d4a 100644 --- a/libs/components/src/color-password/color-password.stories.ts +++ b/libs/components/src/color-password/color-password.stories.ts @@ -14,7 +14,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/6fvTDa3zfvgWdizLQ7nSTP/Numbered-Password", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21540-46261&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 2b42faeccca..e7c5a17c308 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -93,7 +93,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-30495&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/dialog/dialog.stories.ts b/libs/components/src/dialog/dialog/dialog.stories.ts index 7cb6f40aa5b..03a88458f5a 100644 --- a/libs/components/src/dialog/dialog/dialog.stories.ts +++ b/libs/components/src/dialog/dialog/dialog.stories.ts @@ -68,7 +68,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-30495&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts index 9953fdd24ea..b4bf199358b 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts @@ -176,7 +176,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21514-19247&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts index 3bc4999878c..680ebe9ed3b 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts @@ -89,7 +89,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21514-19247&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts index d86b56101b0..50016ef358d 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.stories.ts @@ -17,7 +17,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21514-19247&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/disclosure/disclosure.stories.ts b/libs/components/src/disclosure/disclosure.stories.ts index 974589a667c..bb3680c1f3b 100644 --- a/libs/components/src/disclosure/disclosure.stories.ts +++ b/libs/components/src/disclosure/disclosure.stories.ts @@ -13,6 +13,12 @@ export default { imports: [DisclosureTriggerForDirective, DisclosureComponent, IconButtonModule], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21662-47329&t=k6OTDDPZOTtypRqo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/form-field/bit-validators.stories.ts b/libs/components/src/form-field/bit-validators.stories.ts index 642ff30bb5a..748f92f2523 100644 --- a/libs/components/src/form-field/bit-validators.stories.ts +++ b/libs/components/src/form-field/bit-validators.stories.ts @@ -35,7 +35,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/error-summary.stories.ts b/libs/components/src/form-field/error-summary.stories.ts index 4e1031abaf6..4b1a30c45b3 100644 --- a/libs/components/src/form-field/error-summary.stories.ts +++ b/libs/components/src/form-field/error-summary.stories.ts @@ -34,7 +34,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index 6d8323e088e..738ac96bf76 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -108,7 +108,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/multi-select.stories.ts b/libs/components/src/form-field/multi-select.stories.ts index da4776ab025..8d84aa735bf 100644 --- a/libs/components/src/form-field/multi-select.stories.ts +++ b/libs/components/src/form-field/multi-select.stories.ts @@ -56,7 +56,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=5600%3A24278", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/form-field/password-input-toggle.stories.ts b/libs/components/src/form-field/password-input-toggle.stories.ts index 094f939e0ea..d46ec92ab37 100644 --- a/libs/components/src/form-field/password-input-toggle.stories.ts +++ b/libs/components/src/form-field/password-input-toggle.stories.ts @@ -27,7 +27,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/f32LSg3jaegICkMu7rPARm/Tailwind-Component-Library-Update?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, docs: { description: { diff --git a/libs/components/src/form/form.stories.ts b/libs/components/src/form/form.stories.ts index 23b2cc8cea2..6aef140fe5f 100644 --- a/libs/components/src/form/form.stories.ts +++ b/libs/components/src/form/form.stories.ts @@ -64,7 +64,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17689", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/icon-button/icon-button.stories.ts b/libs/components/src/icon-button/icon-button.stories.ts index 6274bb2b8d1..08c95c5d641 100644 --- a/libs/components/src/icon-button/icon-button.stories.ts +++ b/libs/components/src/icon-button/icon-button.stories.ts @@ -13,7 +13,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4369%3A16686", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-37011&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/icon/icon.stories.ts b/libs/components/src/icon/icon.stories.ts index 54cdd7928cd..53454567b7f 100644 --- a/libs/components/src/icon/icon.stories.ts +++ b/libs/components/src/icon/icon.stories.ts @@ -6,6 +6,12 @@ import * as GenericIcons from "./icons"; export default { title: "Component Library/Icon", component: BitIconComponent, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21662-50335&t=k6OTDDPZOTtypRqo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/item/item.stories.ts b/libs/components/src/item/item.stories.ts index 5adf9d3c49d..3a64a334d0a 100644 --- a/libs/components/src/item/item.stories.ts +++ b/libs/components/src/item/item.stories.ts @@ -53,6 +53,12 @@ export default { }), componentWrapperDecorator((story) => `
    ${story}
    `), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-37011&t=b5tDKylm5sWm2yKo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/layout/layout.stories.ts b/libs/components/src/layout/layout.stories.ts index 7fdad655548..e09055df596 100644 --- a/libs/components/src/layout/layout.stories.ts +++ b/libs/components/src/layout/layout.stories.ts @@ -31,6 +31,10 @@ export default { ], parameters: { chromatic: { viewports: [640, 1280] }, + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21662-51009&t=k6OTDDPZOTtypRqo-11", + }, }, } as Meta; diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index cc3d26dc9d2..d07d33ae589 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -19,7 +19,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17419", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-39582&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/menu/menu.stories.ts b/libs/components/src/menu/menu.stories.ts index 65fafd2d04d..f1f4d8df000 100644 --- a/libs/components/src/menu/menu.stories.ts +++ b/libs/components/src/menu/menu.stories.ts @@ -17,7 +17,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17952", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40144&t=b5tDKylm5sWm2yKo-11", }, }, } as Meta; diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index f412dbc20ba..6fbb89d4d9e 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -62,7 +62,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4687%3A86642", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40145&t=b5tDKylm5sWm2yKo-4", }, chromatic: { viewports: [640, 1280] }, }, diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 376f121eb00..0f6f406b2eb 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -39,7 +39,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=4687%3A86642", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40145&t=b5tDKylm5sWm2yKo-4", }, chromatic: { viewports: [640, 1280] }, }, diff --git a/libs/components/src/no-items/no-items.stories.ts b/libs/components/src/no-items/no-items.stories.ts index d8e5b59bdbf..48d52476b17 100644 --- a/libs/components/src/no-items/no-items.stories.ts +++ b/libs/components/src/no-items/no-items.stories.ts @@ -13,6 +13,12 @@ export default { imports: [ButtonModule, NoItemsModule], }), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21665-25102&t=k6OTDDPZOTtypRqo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/popover/popover.stories.ts b/libs/components/src/popover/popover.stories.ts index 10b7b248b79..fca4d659607 100644 --- a/libs/components/src/popover/popover.stories.ts +++ b/libs/components/src/popover/popover.stories.ts @@ -30,7 +30,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1717-15868", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40852&t=b5tDKylm5sWm2yKo-4", }, }, argTypes: { diff --git a/libs/components/src/progress/progress.stories.ts b/libs/components/src/progress/progress.stories.ts index 49a5398d2d9..1484dab0a21 100644 --- a/libs/components/src/progress/progress.stories.ts +++ b/libs/components/src/progress/progress.stories.ts @@ -8,7 +8,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A18185&t=AM0acaIJ00BUhZKz-4", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-40933&t=b5tDKylm5sWm2yKo-4", }, }, args: { diff --git a/libs/components/src/radio-button/radio-button.stories.ts b/libs/components/src/radio-button/radio-button.stories.ts index dcef13a19be..f5d7f6732f5 100644 --- a/libs/components/src/radio-button/radio-button.stories.ts +++ b/libs/components/src/radio-button/radio-button.stories.ts @@ -31,7 +31,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=3930%3A16850&t=xXPx6GJYsJfuMQPE-4", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-35836&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/section/section.stories.ts b/libs/components/src/section/section.stories.ts index 0d36d6e5a11..53e6bc078c5 100644 --- a/libs/components/src/section/section.stories.ts +++ b/libs/components/src/section/section.stories.ts @@ -22,6 +22,12 @@ export default { }), componentWrapperDecorator((story) => `
    ${story}
    `), ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=21666-19363&t=k6OTDDPZOTtypRqo-11", + }, + }, } as Meta; type Story = StoryObj; diff --git a/libs/components/src/select/select.stories.ts b/libs/components/src/select/select.stories.ts index 4bc85d8dba2..c030bea86c5 100644 --- a/libs/components/src/select/select.stories.ts +++ b/libs/components/src/select/select.stories.ts @@ -33,7 +33,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/3tWtMSYoLB0ZLEimLNzYsm/End-user-%26-admin-Vault-Refresh?t=7QEmGA69YTOF8sXU-0", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/table/table.stories.ts b/libs/components/src/table/table.stories.ts index 4ebc3045d13..e8ab24ee8b7 100644 --- a/libs/components/src/table/table.stories.ts +++ b/libs/components/src/table/table.stories.ts @@ -21,7 +21,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A18371", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-41282&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/tabs/tabs.stories.ts b/libs/components/src/tabs/tabs.stories.ts index 6b460d8ee00..250a7443065 100644 --- a/libs/components/src/tabs/tabs.stories.ts +++ b/libs/components/src/tabs/tabs.stories.ts @@ -74,7 +74,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17922", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-41432&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts index 382e19097b0..abb737f5c23 100644 --- a/libs/components/src/toast/toast.stories.ts +++ b/libs/components/src/toast/toast.stories.ts @@ -63,7 +63,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=16329-41506&t=b5tDKylm5sWm2yKo-11", }, }, } as Meta; diff --git a/libs/components/src/toggle-group/toggle-group.stories.ts b/libs/components/src/toggle-group/toggle-group.stories.ts index fc8ea0ea929..4860636c159 100644 --- a/libs/components/src/toggle-group/toggle-group.stories.ts +++ b/libs/components/src/toggle-group/toggle-group.stories.ts @@ -19,7 +19,7 @@ export default { parameters: { design: { type: "figma", - url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A17157", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881-17157&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; From 83802fcdc51d95756e585e8f494738e73b9a20fe Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 17 Jan 2025 17:01:23 +0100 Subject: [PATCH 241/270] Fix i18n import of drawer component (#12932) Resolves main being broken after merging #12831 due to a new component depending on `I18nPipe`. --- libs/components/src/drawer/drawer-header.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/components/src/drawer/drawer-header.component.ts b/libs/components/src/drawer/drawer-header.component.ts index 73834b8487e..de112a448cf 100644 --- a/libs/components/src/drawer/drawer-header.component.ts +++ b/libs/components/src/drawer/drawer-header.component.ts @@ -1,8 +1,9 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, HostBinding, input } from "@angular/core"; +import { I18nPipe } from "@bitwarden/ui-common"; + import { IconButtonModule } from "../icon-button"; -import { I18nPipe } from "../shared/i18n.pipe"; import { TypographyModule } from "../typography"; import { DrawerCloseDirective } from "./drawer-close.directive"; From dbb341141a721299637ec3ffaf37ec909d584f2a Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 17 Jan 2025 09:10:28 -0800 Subject: [PATCH 242/270] [PM-15572] - don't allow restore for 'edit except password' permission (#12851) * don't allow restore for 'edit except password' permission * show login credentials if only passkey is present * Revert "show login credentials if only passkey is present" This reverts commit dc2f2367c2092cb827cf95fe8c7bc82919949627. --- .../popup/components/vault-v2/view-v2/view-v2.component.ts | 6 +++++- .../trash-list-items-container.component.html | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 6532fb004cb..f3cd713dd5f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -235,7 +235,11 @@ export class ViewV2Component { } protected showFooter(): boolean { - return this.cipher && (!this.cipher.isDeleted || (this.cipher.isDeleted && this.cipher.edit)); + return ( + this.cipher && + (!this.cipher.isDeleted || + (this.cipher.isDeleted && this.cipher.edit && this.cipher.viewPassword)) + ); } /** diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html index dcbda9fd96a..c77ac4f9755 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html @@ -31,7 +31,7 @@ > {{ cipher.subTitle }} - +
    - + + @@ -62,7 +65,7 @@ type="button" buttonType="secondary" (click)="cancel()" - *ngIf="!showCipherView" + *ngIf="!showCipherView && !showRestore" > {{ "cancel" | i18n }} diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 59638aad653..a530fd0cc85 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -49,6 +49,8 @@ import { AttachmentDialogResult, AttachmentsV2Component, } from "../../individual-vault/attachments-v2.component"; +import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; +import { RoutedVaultFilterModel } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { WebCipherFormGenerationService } from "../../services/web-cipher-form-generation.service"; import { WebVaultPremiumUpgradePromptService } from "../../services/web-premium-upgrade-prompt.service"; import { WebViewPasswordHistoryService } from "../../services/web-view-password-history.service"; @@ -82,6 +84,11 @@ export interface VaultItemDialogParams { * If true, the dialog is being opened from the admin console. */ isAdminConsoleAction?: boolean; + + /** + * Function to restore a cipher from the trash. + */ + restore: (c: CipherView) => Promise; } export enum VaultItemDialogResult { @@ -99,6 +106,11 @@ export enum VaultItemDialogResult { * The dialog was closed to navigate the user the premium upgrade page. */ PremiumUpgrade = "premiumUpgrade", + + /** + * A cipher was restored + */ + Restored = "restored", } @Component({ @@ -121,6 +133,7 @@ export enum VaultItemDialogResult { { provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService }, { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, { provide: CipherFormGenerationService, useClass: WebCipherFormGenerationService }, + RoutedVaultFilterService, ], }) export class VaultItemDialogComponent implements OnInit, OnDestroy { @@ -191,6 +204,20 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { ), ); + /** + * Determines if the user may restore the item. + * A user may restore items if they have delete permissions and the item is in the trash. + */ + protected async canUserRestore() { + return ( + this.filter?.type === "trash" && + this.cipher?.isDeleted && + (await firstValueFrom(this.canDeleteCipher$)) + ); + } + + protected showRestore: boolean; + protected get loadingForm() { return this.loadForm && !this.formReady; } @@ -230,6 +257,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { protected canDeleteCipher$: Observable; + protected filter: RoutedVaultFilterModel; + constructor( @Inject(DIALOG_DATA) protected params: VaultItemDialogParams, private dialogRef: DialogRef, @@ -246,6 +275,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { private cipherAuthorizationService: CipherAuthorizationService, private apiService: ApiService, private eventCollectionService: EventCollectionService, + private routedVaultFilterService: RoutedVaultFilterService, ) { this.updateTitle(); } @@ -283,6 +313,9 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { ); } + this.filter = await firstValueFrom(this.routedVaultFilterService.filter$); + + this.showRestore = await this.canUserRestore(); this.performingInitialLoad = false; } @@ -336,6 +369,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this._formReadySubject.next(); } + restore = async () => { + await this.params.restore(this.cipher); + this.dialogRef.close(VaultItemDialogResult.Restored); + }; + delete = async () => { if (!this.cipher) { return; diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 8c1d08b269c..46c678fd987 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -717,6 +717,7 @@ export class VaultComponent implements OnInit, OnDestroy { mode, formConfig, activeCollectionId, + restore: this.restore, }); const result = await lastValueFrom(this.vaultItemDialogRef.closed); diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 14550968ba5..779266d830f 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -994,6 +994,7 @@ export class VaultComponent implements OnInit, OnDestroy { disableForm, activeCollectionId, isAdminConsoleAction: true, + restore: this.restore, }); const result = await lastValueFrom(this.vaultItemDialogRef.closed); @@ -1033,7 +1034,7 @@ export class VaultComponent implements OnInit, OnDestroy { }); } - async restore(c: CipherView): Promise { + restore = async (c: CipherView): Promise => { if (!c.isDeleted) { return; } @@ -1064,7 +1065,7 @@ export class VaultComponent implements OnInit, OnDestroy { } catch (e) { this.logService.error(e); } - } + }; async bulkRestore(ciphers: CipherView[]) { if ( From ce2ec07f74f251f01fb39f330284b6cf8e716148 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:32:29 +0100 Subject: [PATCH 245/270] [PM-17138][Defect] Submitting blank payment method is displaying unreadable validation message for Bank Account and Account Credit (#12937) * Changes to display descriptive message for bank account * resolve message for invalid message display * Add the paymentMethodType credit --------- Co-authored-by: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> --- .../organization-plans.component.ts | 1 + .../shared/payment/payment-v2.component.ts | 32 +++++++++++++------ .../manage-tax-information.component.ts | 4 +++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index edc29b16049..94e8637e8a3 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -605,6 +605,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { submit = async () => { if (this.taxComponent && !this.taxComponent.validate()) { + this.taxComponent.markAllAsTouched(); return; } diff --git a/apps/web/src/app/billing/shared/payment/payment-v2.component.ts b/apps/web/src/app/billing/shared/payment/payment-v2.component.ts index 10cf7ccb702..f65a5743c35 100644 --- a/apps/web/src/app/billing/shared/payment/payment-v2.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment-v2.component.ts @@ -113,16 +113,21 @@ export class PaymentV2Component implements OnInit, OnDestroy { const clientSecret = await this.billingApiService.createSetupIntent(type); if (this.usingBankAccount) { - const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, { - accountHolderName: this.formGroup.value.bankInformation.accountHolderName, - routingNumber: this.formGroup.value.bankInformation.routingNumber, - accountNumber: this.formGroup.value.bankInformation.accountNumber, - accountHolderType: this.formGroup.value.bankInformation.accountHolderType, - }); - return { - type, - token, - }; + this.formGroup.markAllAsTouched(); + if (this.formGroup.valid) { + const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, { + accountHolderName: this.formGroup.value.bankInformation.accountHolderName, + routingNumber: this.formGroup.value.bankInformation.routingNumber, + accountNumber: this.formGroup.value.bankInformation.accountNumber, + accountHolderType: this.formGroup.value.bankInformation.accountHolderType, + }); + return { + type, + token, + }; + } else { + throw "Invalid input provided, Please ensure all required fields are filled out correctly and try again."; + } } if (this.usingCard) { @@ -142,6 +147,13 @@ export class PaymentV2Component implements OnInit, OnDestroy { }; } + if (this.usingAccountCredit) { + return { + type: PaymentMethodType.Credit, + token: null, + }; + } + return null; } diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index 13a6d2d0cc3..885afb1ae67 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -72,6 +72,10 @@ export class ManageTaxInformationComponent implements OnInit, OnDestroy { } } + markAllAsTouched() { + this.formGroup.markAllAsTouched(); + } + async ngOnInit() { if (this.startWith) { this.formGroup.controls.country.setValue(this.startWith.country); From 8674fb51dba8713a551e7d9a31ecb25f11fd9076 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Fri, 17 Jan 2025 16:05:37 -0600 Subject: [PATCH 246/270] PM-17065 Display critical apps (#12867) --- .../all-applications.component.ts | 15 +++- .../app-at-risk-members-dialog.component.html | 4 +- .../application-table.mock.ts | 56 ------------ .../critical-applications.component.html | 38 ++++---- .../critical-applications.component.ts | 87 +++++++++++++++---- 5 files changed, 101 insertions(+), 99 deletions(-) delete mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index b22b94599f9..627495a6bde 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -57,10 +57,15 @@ export class AllApplicationsComponent implements OnInit { protected selectedUrls: Set = new Set(); protected searchControl = new FormControl("", { nonNullable: true }); protected loading = true; - protected organization = {} as Organization; + protected organization = new Organization(); noItemsIcon = Icons.Security; protected markingAsCritical = false; - protected applicationSummary = {} as ApplicationHealthReportSummary; + protected applicationSummary: ApplicationHealthReportSummary = { + totalMemberCount: 0, + totalAtRiskMemberCount: 0, + totalApplicationCount: 0, + totalAtRiskApplicationCount: 0, + }; destroyRef = inject(DestroyRef); isLoading$: Observable = of(false); @@ -90,8 +95,10 @@ export class AllApplicationsComponent implements OnInit { }), ) .subscribe(({ data, organization }) => { - this.dataSource.data = data ?? []; - this.applicationSummary = this.reportService.generateApplicationsSummary(data ?? []); + if (data) { + this.dataSource.data = data; + this.applicationSummary = this.reportService.generateApplicationsSummary(data); + } if (organization) { this.organization = organization; } diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html index 383a1eccabe..fa58678be00 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html @@ -3,9 +3,7 @@
    {{ "atRiskMembersWithCount" | i18n: members.length }} - {{ - "atRiskMembersDescriptionWithApp" | i18n: applicationName - }} + {{ "atRiskMembersDescriptionWithApp" | i18n: applicationName }}
    {{ member.email }}
    diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts deleted file mode 100644 index 4dffa60b562..00000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts +++ /dev/null @@ -1,56 +0,0 @@ -export const applicationTableMockData = [ - { - id: 1, - name: "google.com", - atRiskPasswords: 4, - totalPasswords: 10, - atRiskMembers: 2, - totalMembers: 5, - isMarkedAsCritical: false, - }, - { - id: 2, - name: "facebook.com", - atRiskPasswords: 3, - totalPasswords: 8, - atRiskMembers: 1, - totalMembers: 3, - isMarkedAsCritical: false, - }, - { - id: 3, - name: "twitter.com", - atRiskPasswords: 2, - totalPasswords: 6, - atRiskMembers: 0, - totalMembers: 2, - isMarkedAsCritical: false, - }, - { - id: 4, - name: "linkedin.com", - atRiskPasswords: 1, - totalPasswords: 4, - atRiskMembers: 0, - totalMembers: 1, - isMarkedAsCritical: false, - }, - { - id: 5, - name: "instagram.com", - atRiskPasswords: 0, - totalPasswords: 2, - atRiskMembers: 0, - totalMembers: 0, - isMarkedAsCritical: false, - }, - { - id: 6, - name: "tiktok.com", - atRiskPasswords: 0, - totalPasswords: 1, - atRiskMembers: 0, - totalMembers: 0, - isMarkedAsCritical: false, - }, -]; diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html index 1c503f3d786..87b21c7c755 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html @@ -35,17 +35,19 @@
    @@ -60,38 +62,38 @@ - {{ "application" | i18n }} - {{ "atRiskPasswords" | i18n }} - {{ "totalPasswords" | i18n }} - {{ "atRiskMembers" | i18n }} - {{ "totalMembers" | i18n }} + {{ "application" | i18n }} + {{ "atRiskPasswords" | i18n }} + {{ "totalPasswords" | i18n }} + {{ "atRiskMembers" | i18n }} + {{ "totalMembers" | i18n }} - + - + - - {{ r.name }} + + {{ r.applicationName }} - {{ r.atRiskPasswords }} + {{ r.atRiskPasswordCount }} - {{ r.totalPasswords }} + {{ r.passwordCount }} - {{ r.atRiskMembers }} + {{ r.atRiskMemberCount }} - {{ r.totalMembers }} + {{ r.memberCount }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts index 99f68aa9c72..450f0d5d660 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.ts @@ -4,16 +4,32 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { debounceTime, map } from "rxjs"; +import { combineLatest, debounceTime, map } from "rxjs"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SearchModule, TableDataSource, NoItemsModule, Icons } from "@bitwarden/components"; +import { + CriticalAppsService, + RiskInsightsDataService, + RiskInsightsReportService, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + ApplicationHealthReportDetailWithCriticalFlag, + ApplicationHealthReportSummary, +} from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { + DialogService, + Icons, + NoItemsModule, + SearchModule, + TableDataSource, +} from "@bitwarden/components"; import { CardComponent } from "@bitwarden/tools-card"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; -import { applicationTableMockData } from "./application-table.mock"; +import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component"; +import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component"; +import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component"; import { RiskInsightsTabType } from "./risk-insights.component"; @Component({ @@ -23,30 +39,38 @@ import { RiskInsightsTabType } from "./risk-insights.component"; imports: [CardComponent, HeaderModule, SearchModule, NoItemsModule, PipesModule, SharedModule], }) export class CriticalApplicationsComponent implements OnInit { - protected dataSource = new TableDataSource(); + protected dataSource = new TableDataSource(); protected selectedIds: Set = new Set(); protected searchControl = new FormControl("", { nonNullable: true }); private destroyRef = inject(DestroyRef); protected loading = false; protected organizationId: string; + protected applicationSummary = {} as ApplicationHealthReportSummary; noItemsIcon = Icons.Security; - // MOCK DATA - protected mockData = applicationTableMockData; - protected mockAtRiskMembersCount = 0; - protected mockAtRiskAppsCount = 0; - protected mockTotalMembersCount = 0; - protected mockTotalAppsCount = 0; ngOnInit() { - this.activatedRoute.paramMap + this.organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; + combineLatest([ + this.dataService.applications$, + this.criticalAppsService.getAppsListForOrg(this.organizationId), + ]) .pipe( takeUntilDestroyed(this.destroyRef), - map(async (params) => { - this.organizationId = params.get("organizationId"); - // TODO: use organizationId to fetch data + map(([applications, criticalApps]) => { + const criticalUrls = criticalApps.map((ca) => ca.uri); + const data = applications?.map((app) => ({ + ...app, + isMarkedAsCritical: criticalUrls.includes(app.applicationName), + })) as ApplicationHealthReportDetailWithCriticalFlag[]; + return data?.filter((app) => app.isMarkedAsCritical); }), ) - .subscribe(); + .subscribe((applications) => { + if (applications) { + this.dataSource.data = applications; + this.applicationSummary = this.reportService.generateApplicationsSummary(applications); + } + }); } goToAllAppsTab = async () => { @@ -57,13 +81,40 @@ export class CriticalApplicationsComponent implements OnInit { }; constructor( - protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, protected router: Router, + protected dataService: RiskInsightsDataService, + protected criticalAppsService: CriticalAppsService, + protected reportService: RiskInsightsReportService, + protected dialogService: DialogService, ) { - this.dataSource.data = []; //applicationTableMockData; this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) .subscribe((v) => (this.dataSource.filter = v)); } + + showAppAtRiskMembers = async (applicationName: string) => { + openAppAtRiskMembersDialog(this.dialogService, { + members: + this.dataSource.data.find((app) => app.applicationName === applicationName) + ?.atRiskMemberDetails ?? [], + applicationName, + }); + }; + + showOrgAtRiskMembers = async () => { + this.dialogService.open(OrgAtRiskMembersDialogComponent, { + data: this.reportService.generateAtRiskMemberList(this.dataSource.data), + }); + }; + + showOrgAtRiskApps = async () => { + this.dialogService.open(OrgAtRiskAppsDialogComponent, { + data: this.reportService.generateAtRiskApplicationList(this.dataSource.data), + }); + }; + + trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) { + return item.applicationName; + } } From a803e5b411e7179783c0c0020ffa2edc66d18038 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:01:18 -0800 Subject: [PATCH 247/270] [PM-6565] migrate vault toasts to CL toastService (#10664) * migrate vault toasts to CL toastService * update component args * add missing toastService deps * add missing i18 key * remove moved files * remove duplicate args --- .../popup/settings/premium-v2.component.ts | 3 + .../vault/app/accounts/premium.component.ts | 4 +- .../app/vault/folder-add-edit.component.ts | 4 +- .../app/vault/password-history.component.ts | 4 +- .../filters/organization-filter.component.ts | 12 +-- .../src/vault/app/vault/vault.component.ts | 13 ++-- .../src/vault/app/vault/view.component.ts | 4 +- .../collection-dialog.component.ts | 46 ++++++----- .../individual-vault/add-edit.component.ts | 10 +-- .../bulk-delete-dialog.component.ts | 43 ++++++----- .../bulk-move-dialog.component.ts | 9 ++- .../bulk-share-dialog.component.ts | 15 ++-- .../folder-add-edit.component.ts | 11 +-- .../organization-options.component.ts | 22 ++++-- .../components/vault-filter.component.ts | 13 ++-- .../bulk-collections-dialog.component.ts | 9 ++- .../vault-filter/vault-filter.component.ts | 4 +- .../vault/settings/purge-vault.component.ts | 9 ++- apps/web/src/locales/en/messages.json | 3 + .../vault/components/add-edit.component.ts | 74 ++++++++++-------- .../vault/components/attachments.component.ts | 76 ++++++++++++------- .../components/folder-add-edit.component.ts | 29 ++++--- .../components/password-history.component.ts | 12 +-- .../src/vault/components/premium.component.ts | 9 ++- .../src/vault/components/view.component.ts | 69 +++++++++++------ .../components/password-reprompt.component.ts | 12 +-- 26 files changed, 317 insertions(+), 202 deletions(-) diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.ts b/apps/browser/src/billing/popup/settings/premium-v2.component.ts index f658f71a209..da3d2c07e4b 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -20,6 +20,7 @@ import { DialogService, ItemModule, SectionComponent, + ToastService, } from "@bitwarden/components"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; @@ -57,6 +58,7 @@ export class PremiumV2Component extends BasePremiumComponent { dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + toastService: ToastService, accountService: AccountService, ) { super( @@ -68,6 +70,7 @@ export class PremiumV2Component extends BasePremiumComponent { dialogService, environmentService, billingAccountProfileStateService, + toastService, accountService, ); diff --git a/apps/desktop/src/vault/app/accounts/premium.component.ts b/apps/desktop/src/vault/app/accounts/premium.component.ts index 4b547384545..bfdaf084648 100644 --- a/apps/desktop/src/vault/app/accounts/premium.component.ts +++ b/apps/desktop/src/vault/app/accounts/premium.component.ts @@ -9,7 +9,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-premium", @@ -25,6 +25,7 @@ export class PremiumComponent extends BasePremiumComponent { dialogService: DialogService, environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + toastService: ToastService, accountService: AccountService, ) { super( @@ -36,6 +37,7 @@ export class PremiumComponent extends BasePremiumComponent { dialogService, environmentService, billingAccountProfileStateService, + toastService, accountService, ); } diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts index 1cab5a940dd..cdb879693c0 100644 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts @@ -8,7 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @Component({ @@ -26,6 +26,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { logService: LogService, dialogService: DialogService, formBuilder: FormBuilder, + toastService: ToastService, ) { super( folderService, @@ -37,6 +38,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { logService, dialogService, formBuilder, + toastService, ); } } diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 12701ac5527..4a87617d8f4 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -5,6 +5,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-password-history", @@ -16,7 +17,8 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, accountService: AccountService, + toastService: ToastService, ) { - super(cipherService, platformUtilsService, i18nService, accountService, window); + super(cipherService, platformUtilsService, i18nService, accountService, window, toastService); } } diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts index 92c75e30417..39f1c0200ea 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts @@ -7,6 +7,7 @@ import { DisplayMode } from "@bitwarden/angular/vault/vault-filter/models/displa import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-organization-filter", @@ -25,6 +26,7 @@ export class OrganizationFilterComponent extends BaseOrganizationFilterComponent constructor( private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) { super(); } @@ -36,11 +38,11 @@ export class OrganizationFilterComponent extends BaseOrganizationFilterComponent // eslint-disable-next-line @typescript-eslint/no-floating-promises super.applyOrganizationFilter(organization); } else { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("disabledOrganizationFilterError"), - ); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("disabledOrganizationFilterError"), + }); } } } diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index c2260692fbd..da1f7bb3160 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -35,7 +35,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault"; import { SearchBarService } from "../../../app/layout/search/search-bar.service"; @@ -113,6 +113,7 @@ export class VaultComponent implements OnInit, OnDestroy { private apiService: ApiService, private dialogService: DialogService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private toastService: ToastService, private configService: ConfigService, private accountService: AccountService, private cipherService: CipherService, @@ -809,11 +810,11 @@ export class VaultComponent implements OnInit, OnDestroy { } this.platformUtilsService.copyToClipboard(value); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(labelI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(labelI18nKey)), + }); if (this.action === "view") { this.messagingService.send("minimizeOnCopy"); } diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index d3e8fff3495..ce9d3af8276 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -31,7 +31,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault"; @@ -68,6 +68,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro datePipe: DatePipe, billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, + toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, ) { super( @@ -94,6 +95,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro datePipe, accountService, billingAccountProfileStateService, + toastService, cipherAuthorizationService, ); } diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index 42d033dc4c2..6141e983a68 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -29,7 +29,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { BitValidators, DialogService } from "@bitwarden/components"; +import { BitValidators, DialogService, ToastService } from "@bitwarden/components"; import { GroupApiService, GroupView } from "../../../admin-console/organizations/core"; import { PermissionMode } from "../../../admin-console/organizations/shared/components/access-selector/access-selector.component"; @@ -110,6 +110,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, + private toastService: ToastService, ) { this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info; } @@ -274,17 +275,20 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { const accessTabError = this.formGroup.controls.access.hasError("managePermissionRequired"); if (this.tabIndex === CollectionDialogTabType.Access && !accessTabError) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("fieldOnTabRequiresAttention", this.i18nService.t("collectionInfo")), - ); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t( + "fieldOnTabRequiresAttention", + this.i18nService.t("collectionInfo"), + ), + }); } else if (this.tabIndex === CollectionDialogTabType.Info && accessTabError) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("fieldOnTabRequiresAttention", this.i18nService.t("access")), - ); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("fieldOnTabRequiresAttention", this.i18nService.t("access")), + }); } return; } @@ -309,14 +313,14 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { const savedCollection = await this.collectionAdminService.save(collectionView); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t( + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t( this.editMode ? "editedCollectionId" : "createdCollectionId", collectionView.name, ), - ); + }); this.close(CollectionDialogAction.Saved, savedCollection); }; @@ -339,11 +343,11 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { await this.collectionAdminService.delete(this.params.organizationId, this.params.collectionId); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("deletedCollectionId", this.collection?.name), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedCollectionId", this.collection?.name), + }); this.close(CollectionDialogAction.Deleted, this.collection); }; diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 916c845e9d3..62ce55848f5 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -194,11 +194,11 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On } this.platformUtilsService.copyToClipboard(value, { window: window }); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (this.editMode) { if (typeI18nKey === "password") { diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 913f106004d..becfcb8f588 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -10,7 +10,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherBulkDeleteRequest } from "@bitwarden/common/vault/models/request/cipher-bulk-delete.request"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface BulkDeleteDialogParams { cipherIds?: string[]; @@ -60,6 +60,7 @@ export class BulkDeleteDialogComponent { private i18nService: I18nService, private apiService: ApiService, private collectionService: CollectionService, + private toastService: ToastService, ) { this.cipherIds = params.cipherIds ?? []; this.permanent = params.permanent; @@ -95,19 +96,19 @@ export class BulkDeleteDialogComponent { await Promise.all(deletePromises); if (this.cipherIds.length || this.unassignedCiphers.length) { - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems"), + }); } if (this.collections.length) { await this.collectionService.delete(this.collections.map((c) => c.id)); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("deletedCollections"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedCollections"), + }); } this.close(BulkDeleteDialogResult.Deleted); }; @@ -134,11 +135,11 @@ export class BulkDeleteDialogComponent { // From org vault if (this.organization) { if (this.collections.some((c) => !c.canDelete(this.organization))) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("missingPermissions"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("missingPermissions"), + }); return; } return await this.apiService.deleteManyCollections( @@ -151,11 +152,11 @@ export class BulkDeleteDialogComponent { for (const organization of this.organizations) { const orgCollections = this.collections.filter((o) => o.organizationId === organization.id); if (orgCollections.some((c) => !c.canDelete(organization))) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("missingPermissions"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("missingPermissions"), + }); return; } const orgCollectionIds = orgCollections.map((c) => c.id); diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index b7f99fb7b44..295d3ccc435 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -11,7 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface BulkMoveDialogParams { cipherIds?: string[]; @@ -58,6 +58,7 @@ export class BulkMoveDialogComponent implements OnInit { private i18nService: I18nService, private folderService: FolderService, private formBuilder: FormBuilder, + private toastService: ToastService, private accountService: AccountService, ) { this.cipherIds = params.cipherIds ?? []; @@ -81,7 +82,11 @@ export class BulkMoveDialogComponent implements OnInit { } await this.cipherService.moveManyWithServer(this.cipherIds, this.formGroup.value.folderId); - this.platformUtilsService.showToast("success", null, this.i18nService.t("movedItems")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("movedItems"), + }); this.close(BulkMoveDialogResult.Moved); }; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts index 62798c21bca..1dc15a7471a 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts @@ -10,11 +10,10 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Checkable, isChecked } from "@bitwarden/common/types/checkable"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface BulkShareDialogParams { ciphers: CipherView[]; @@ -59,12 +58,12 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { @Inject(DIALOG_DATA) params: BulkShareDialogParams, private dialogRef: DialogRef, private cipherService: CipherService, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private collectionService: CollectionService, private organizationService: OrganizationService, private logService: LogService, private accountService: AccountService, + private toastService: ToastService, ) { this.ciphers = params.ciphers ?? []; this.organizationId = params.organizationId; @@ -114,11 +113,11 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { const orgName = this.organizations.find((o) => o.id === this.organizationId)?.name ?? this.i18nService.t("organization"); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("movedItemsToOrg", orgName), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("movedItemsToOrg", orgName), + }); this.close(BulkShareDialogResult.Shared); } catch (e) { this.logService.error(e); diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index bc639ff2f61..272cfb130b9 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -45,6 +45,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { logService, dialogService, formBuilder, + toastService, ); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-expressions @@ -89,11 +90,11 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { const folder = await this.folderService.encrypt(this.folder, userKey); this.formPromise = this.folderApiService.save(folder, activeAccountId); await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), + }); this.onSavedFolder.emit(this.folder); this.dialogRef.close(FolderAddEditDialogResult.Saved); } catch (e) { 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 6788471dd04..6255ee11c49 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 @@ -146,7 +146,11 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { return this.syncService.fullSync(true); }); await this.actionPromise; - this.platformUtilsService.showToast("success", null, "Unlinked SSO"); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("unlinkedSso"), + }); } catch (e) { this.logService.error(e); } @@ -166,7 +170,11 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { try { this.actionPromise = this.organizationApiService.leave(org.id); await this.actionPromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("leftOrganization"), + }); } catch (e) { this.logService.error(e); } @@ -199,11 +207,11 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { ); try { await this.actionPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("withdrawPasswordResetSuccess"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("withdrawPasswordResetSuccess"), + }); await this.syncService.fullSync(true); } catch (e) { this.logService.error(e); 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 f568ba159a6..f1241ef3280 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 @@ -13,7 +13,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { TrialFlowService } from "../../../../billing/services/trial-flow.service"; import { VaultFilterService } from "../services/abstractions/vault-filter.service"; @@ -98,6 +98,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { protected policyService: PolicyService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, + protected toastService: ToastService, protected billingApiService: BillingApiServiceAbstraction, protected dialogService: DialogService, protected configService: ConfigService, @@ -122,11 +123,11 @@ export class VaultFilterComponent implements OnInit, OnDestroy { applyOrganizationFilter = async (orgNode: TreeNode): Promise => { if (!orgNode?.node.enabled) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("disabledOrganizationFilterError"), - ); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("disabledOrganizationFilterError"), + }); const metadata = await this.billingApiService.getOrganizationBillingMetadata(orgNode.node.id); await this.trialFlowService.handleUnpaidSubscriptionDialog(orgNode.node, metadata); } diff --git a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts index 0fc7b6a31aa..d9ba8af49fa 100644 --- a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -14,7 +14,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { GroupApiService, GroupView } from "../../../admin-console/organizations/core"; import { @@ -68,6 +68,7 @@ export class BulkCollectionsDialogComponent implements OnDestroy { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private collectionAdminService: CollectionAdminService, + private toastService: ToastService, ) { this.numCollections = this.params.collections.length; const organization$ = this.organizationService.get$(this.params.organizationId); @@ -119,7 +120,11 @@ export class BulkCollectionsDialogComponent implements OnDestroy { groups, ); - this.platformUtilsService.showToast("success", null, this.i18nService.t("editedCollections")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("editedCollections"), + }); this.dialogRef.close(BulkCollectionsDialogResult.Saved); }; diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts index 0a8a6c9da94..7e08af7c7f7 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.component.ts @@ -10,7 +10,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { VaultFilterComponent as BaseVaultFilterComponent } from "../../individual-vault/vault-filter/components/vault-filter.component"; //../../vault/vault-filter/components/vault-filter.component"; import { VaultFilterService } from "../../individual-vault/vault-filter/services/abstractions/vault-filter.service"; @@ -43,6 +43,7 @@ export class VaultFilterComponent protected policyService: PolicyService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, + protected toastService: ToastService, protected billingApiService: BillingApiServiceAbstraction, protected dialogService: DialogService, protected configService: ConfigService, @@ -52,6 +53,7 @@ export class VaultFilterComponent policyService, i18nService, platformUtilsService, + toastService, billingApiService, dialogService, configService, diff --git a/apps/web/src/app/vault/settings/purge-vault.component.ts b/apps/web/src/app/vault/settings/purge-vault.component.ts index 80b0448a39c..83955c0dc94 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -11,7 +11,7 @@ import { Verification } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; export interface PurgeVaultDialogData { organizationId: string; @@ -37,6 +37,7 @@ export class PurgeVaultComponent { private userVerificationService: UserVerificationService, private router: Router, private syncService: SyncService, + private toastService: ToastService, ) { this.organizationId = data && data.organizationId ? data.organizationId : null; } @@ -46,7 +47,11 @@ export class PurgeVaultComponent { .buildRequest(this.formGroup.value.masterPassword) .then((request) => this.apiService.postPurgeCiphers(request, this.organizationId)); await response; - this.platformUtilsService.showToast("success", null, this.i18nService.t("vaultPurged")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("vaultPurged"), + }); await this.syncService.fullSync(true); if (this.organizationId != null) { await this.router.navigate(["organizations", this.organizationId, "vault"]); diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 15c5a7fcf6c..95378c90c97 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3789,6 +3789,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index bf2e68b71cd..b86d48d3911 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -372,11 +372,11 @@ export class AddEditComponent implements OnInit, OnDestroy { } if (this.cipher.name == null || this.cipher.name === "") { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("nameRequired"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("nameRequired"), + }); return false; } @@ -385,11 +385,11 @@ export class AddEditComponent implements OnInit, OnDestroy { !this.allowPersonal && this.cipher.organizationId == null ) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("personalOwnershipSubmitError"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("personalOwnershipSubmitError"), + }); return false; } @@ -424,11 +424,11 @@ export class AddEditComponent implements OnInit, OnDestroy { this.formPromise = this.saveCipher(cipher); await this.formPromise; this.cipher.id = cipher.id; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem"), + }); this.onSavedCipher.emit(this.cipher); this.messagingService.send(this.editMode && !this.cloneMode ? "editedCipher" : "addedCipher"); return true; @@ -514,11 +514,13 @@ export class AddEditComponent implements OnInit, OnDestroy { try { this.deletePromise = this.deleteCipher(); await this.deletePromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t( + this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", + ), + }); this.onDeletedCipher.emit(this.cipher); this.messagingService.send( this.cipher.isDeleted ? "permanentlyDeletedCipher" : "deletedCipher", @@ -538,7 +540,11 @@ export class AddEditComponent implements OnInit, OnDestroy { try { this.restorePromise = this.restoreCipher(); await this.restorePromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); this.onRestoredCipher.emit(this.cipher); this.messagingService.send("restoredCipher"); } catch (e) { @@ -679,13 +685,17 @@ export class AddEditComponent implements OnInit, OnDestroy { this.checkPasswordPromise = null; if (matches > 0) { - this.platformUtilsService.showToast( - "warning", - null, - this.i18nService.t("passwordExposed", matches.toString()), - ); + this.toastService.showToast({ + variant: "warning", + title: null, + message: this.i18nService.t("passwordExposed", matches.toString()), + }); } else { - this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("passwordSafe"), + }); } } @@ -779,11 +789,11 @@ export class AddEditComponent implements OnInit, OnDestroy { const copyOptions = this.win != null ? { window: this.win } : null; this.platformUtilsService.copyToClipboard(value, copyOptions); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (typeI18nKey === "password") { void this.eventCollectionService.collectMany(EventType.Cipher_ClientCopiedPassword, [ diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 1a4c428aae2..a3b635f151d 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -64,21 +64,21 @@ export class AttachmentsComponent implements OnInit { const fileEl = document.getElementById("file") as HTMLInputElement; const files = fileEl.files; if (files == null || files.length === 0) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("selectFile"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("selectFile"), + }); return; } if (files[0].size > 524288000) { // 500 MB - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("maxFileSize"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("maxFileSize"), + }); return; } @@ -91,7 +91,11 @@ export class AttachmentsComponent implements OnInit { this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); - this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("attachmentSaved"), + }); this.onUploadedAttachment.emit(); } catch (e) { this.logService.error(e); @@ -122,7 +126,11 @@ export class AttachmentsComponent implements OnInit { try { this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); await this.deletePromises[attachment.id]; - this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedAttachment")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedAttachment"), + }); const i = this.cipher.attachments.indexOf(attachment); if (i > -1) { this.cipher.attachments.splice(i, 1); @@ -142,11 +150,11 @@ export class AttachmentsComponent implements OnInit { } if (!this.canAccessAttachments) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("premiumRequired"), - this.i18nService.t("premiumRequiredDesc"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("premiumRequired"), + message: this.i18nService.t("premiumRequiredDesc"), + }); return; } @@ -171,7 +179,11 @@ export class AttachmentsComponent implements OnInit { a.downloading = true; const response = await fetch(new Request(url, { cache: "no-store" })); if (response.status !== 200) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); a.downloading = false; return; } @@ -195,7 +207,11 @@ export class AttachmentsComponent implements OnInit { // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); } a.downloading = false; @@ -243,7 +259,11 @@ export class AttachmentsComponent implements OnInit { a.downloading = true; const response = await fetch(new Request(attachment.url, { cache: "no-store" })); if (response.status !== 200) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); a.downloading = false; return; } @@ -281,16 +301,20 @@ export class AttachmentsComponent implements OnInit { } } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("attachmentSaved"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("attachmentSaved"), + }); this.onReuploadedAttachment.emit(); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); } a.downloading = false; diff --git a/libs/angular/src/vault/components/folder-add-edit.component.ts b/libs/angular/src/vault/components/folder-add-edit.component.ts index 205733ba48d..28ed0dc2aed 100644 --- a/libs/angular/src/vault/components/folder-add-edit.component.ts +++ b/libs/angular/src/vault/components/folder-add-edit.component.ts @@ -11,7 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @Directive() @@ -43,6 +43,7 @@ export class FolderAddEditComponent implements OnInit { protected logService: LogService, protected dialogService: DialogService, protected formBuilder: FormBuilder, + protected toastService: ToastService, ) {} async ngOnInit() { @@ -52,11 +53,11 @@ export class FolderAddEditComponent implements OnInit { async submit(): Promise { this.folder.name = this.formGroup.controls.name.value; if (this.folder.name == null || this.folder.name === "") { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("nameRequired"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("nameRequired"), + }); return false; } @@ -66,11 +67,11 @@ export class FolderAddEditComponent implements OnInit { const folder = await this.folderService.encrypt(this.folder, userKey); this.formPromise = this.folderApiService.save(folder, activeUserId); await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder"), + }); this.onSavedFolder.emit(this.folder); return true; } catch (e) { @@ -95,7 +96,11 @@ export class FolderAddEditComponent implements OnInit { const activeUserId = await firstValueFrom(this.activeUserId$); this.deletePromise = this.folderApiService.delete(this.folder.id, activeUserId); await this.deletePromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedFolder"), + }); this.onDeletedFolder.emit(this.folder); } catch (e) { this.logService.error(e); diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 942a34c58bb..0b385688d0b 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -8,6 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view"; +import { ToastService } from "@bitwarden/components"; @Directive() export class PasswordHistoryComponent implements OnInit { @@ -20,6 +21,7 @@ export class PasswordHistoryComponent implements OnInit { protected i18nService: I18nService, protected accountService: AccountService, private win: Window, + private toastService: ToastService, ) {} async ngOnInit() { @@ -29,11 +31,11 @@ export class PasswordHistoryComponent implements OnInit { copy(password: string) { const copyOptions = this.win != null ? { window: this.win } : null; this.platformUtilsService.copyToClipboard(password, copyOptions); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t("password")), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t("password")), + }); } protected async init() { diff --git a/libs/angular/src/vault/components/premium.component.ts b/libs/angular/src/vault/components/premium.component.ts index 8b1f215ef42..e86c6beda47 100644 --- a/libs/angular/src/vault/components/premium.component.ts +++ b/libs/angular/src/vault/components/premium.component.ts @@ -12,7 +12,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; +import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components"; @Directive() export class PremiumComponent implements OnInit { @@ -31,6 +31,7 @@ export class PremiumComponent implements OnInit { protected dialogService: DialogService, private environmentService: EnvironmentService, billingAccountProfileStateService: BillingAccountProfileStateService, + private toastService: ToastService, accountService: AccountService, ) { this.isPremium$ = accountService.activeAccount$.pipe( @@ -51,7 +52,11 @@ export class PremiumComponent implements OnInit { try { this.refreshPromise = this.apiService.refreshIdentityToken(); await this.refreshPromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("refreshComplete")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("refreshComplete"), + }); } catch (e) { this.logService.error(e); } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index ef9aff736ed..18caa875e03 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -40,7 +40,7 @@ import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.v import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -115,6 +115,7 @@ export class ViewComponent implements OnDestroy, OnInit { protected datePipe: DatePipe, protected accountService: AccountService, private billingAccountProfileStateService: BillingAccountProfileStateService, + protected toastService: ToastService, private cipherAuthorizationService: CipherAuthorizationService, ) {} @@ -246,11 +247,13 @@ export class ViewComponent implements OnDestroy, OnInit { try { await this.deleteCipher(); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t( + this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", + ), + }); this.onDeletedCipher.emit(this.cipher); } catch (e) { this.logService.error(e); @@ -266,7 +269,11 @@ export class ViewComponent implements OnDestroy, OnInit { try { await this.restoreCipher(); - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); this.onRestoredCipher.emit(this.cipher); } catch (e) { this.logService.error(e); @@ -349,13 +356,17 @@ export class ViewComponent implements OnDestroy, OnInit { const matches = await this.checkPasswordPromise; if (matches > 0) { - this.platformUtilsService.showToast( - "warning", - null, - this.i18nService.t("passwordExposed", matches.toString()), - ); + this.toastService.showToast({ + variant: "warning", + title: null, + message: this.i18nService.t("passwordExposed", matches.toString()), + }); } else { - this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("passwordSafe"), + }); } } @@ -385,11 +396,11 @@ export class ViewComponent implements OnDestroy, OnInit { const copyOptions = this.win != null ? { window: this.win } : null; this.platformUtilsService.copyToClipboard(value, copyOptions); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (typeI18nKey === "password") { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -422,11 +433,11 @@ export class ViewComponent implements OnDestroy, OnInit { } if (this.cipher.organizationId == null && !this.canAccessPremium) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("premiumRequired"), - this.i18nService.t("premiumRequiredDesc"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("premiumRequired"), + message: this.i18nService.t("premiumRequiredDesc"), + }); return; } @@ -450,7 +461,11 @@ export class ViewComponent implements OnDestroy, OnInit { a.downloading = true; const response = await fetch(new Request(url, { cache: "no-store" })); if (response.status !== 200) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); a.downloading = false; return; } @@ -469,7 +484,11 @@ export class ViewComponent implements OnDestroy, OnInit { // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("errorOccurred"), + }); } a.downloading = false; diff --git a/libs/vault/src/components/password-reprompt.component.ts b/libs/vault/src/components/password-reprompt.component.ts index abefac58747..6898d411719 100644 --- a/libs/vault/src/components/password-reprompt.component.ts +++ b/libs/vault/src/components/password-reprompt.component.ts @@ -13,6 +13,7 @@ import { DialogModule, FormFieldModule, IconButtonModule, + ToastService, } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -45,6 +46,7 @@ export class PasswordRepromptComponent { protected i18nService: I18nService, protected formBuilder: FormBuilder, protected dialogRef: DialogRef, + private toastService: ToastService, protected accountService: AccountService, ) {} @@ -72,11 +74,11 @@ export class PasswordRepromptComponent { userId, )) ) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("invalidMasterPassword"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("invalidMasterPassword"), + }); return; } From 43a6a93944367afc1d26f85d0920d9288fe70bad Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:04:06 -0800 Subject: [PATCH 248/270] don't allow 'except password' permissions to view or copy hidden fields (#12899) --- .../components/custom-fields/custom-fields.component.ts | 4 ++++ .../cipher-view/custom-fields/custom-fields-v2.component.html | 2 ++ .../cipher-view/custom-fields/custom-fields-v2.component.ts | 4 ++++ 3 files changed, 10 insertions(+) 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 d723bad5751..cd75fe8fba1 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 @@ -167,6 +167,10 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit { ); }); + if (!this.cipherFormContainer.originalCipherView?.viewPassword) { + this.customFieldsForm.disable(); + } + // Disable the form if in partial-edit mode // Must happen after the initial fields are populated if (this.cipherFormContainer.config.mode === "partial-edit") { diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index 45ddc3c1dea..ab31ede57bb 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -38,6 +38,7 @@ type="button" bitIconButton bitPasswordInputToggle + *ngIf="canViewPassword" (toggledChange)="logHiddenEvent($event)" > diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts index b41b351e197..313607ce3a6 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts @@ -59,6 +59,10 @@ export class CustomFieldV2Component implements OnInit { return this.i18nService.t(linkedType.i18nKey); } + get canViewPassword() { + return this.cipher.viewPassword; + } + async logHiddenEvent(hiddenFieldVisible: boolean) { if (hiddenFieldVisible) { await this.eventCollectionService.collect( From d820bfb69165948d3678fb4f86b97949fac019ef Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 20 Jan 2025 11:43:10 +0100 Subject: [PATCH 249/270] [PM-17346] Move A11yTitle and CopyClick to CL (#12936) * Move A11yTitle and CopyClick to CL --- libs/angular/src/jslib.module.ts | 8 ++++---- .../src/a11y}/a11y-title.directive.ts | 1 + libs/components/src/a11y/index.ts | 1 + .../src/copy-click}/copy-click.directive.spec.ts | 15 +++++++++------ .../src/copy-click}/copy-click.directive.ts | 4 +++- libs/components/src/copy-click/index.ts | 1 + libs/components/src/index.ts | 4 +++- .../login-credentials-view.component.spec.ts | 3 +-- 8 files changed, 23 insertions(+), 14 deletions(-) rename libs/{angular/src/directives => components/src/a11y}/a11y-title.directive.ts (98%) create mode 100644 libs/components/src/a11y/index.ts rename libs/{angular/src/directives => components/src/copy-click}/copy-click.directive.spec.ts (89%) rename libs/{angular/src/directives => components/src/copy-click}/copy-click.directive.ts (96%) create mode 100644 libs/components/src/copy-click/index.ts diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index 4f5a8f6673c..b06faacef19 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -25,15 +25,15 @@ import { TableModule, ToastModule, TypographyModule, + CopyClickDirective, + A11yTitleDirective, } from "@bitwarden/components"; import { TwoFactorIconComponent } from "./auth/components/two-factor-icon.component"; import { DeprecatedCalloutComponent } from "./components/callout.component"; import { A11yInvalidDirective } from "./directives/a11y-invalid.directive"; -import { A11yTitleDirective } from "./directives/a11y-title.directive"; import { ApiActionDirective } from "./directives/api-action.directive"; import { BoxRowDirective } from "./directives/box-row.directive"; -import { CopyClickDirective } from "./directives/copy-click.directive"; import { CopyTextDirective } from "./directives/copy-text.directive"; import { FallbackSrcDirective } from "./directives/fallback-src.directive"; import { IfFeatureDirective } from "./directives/if-feature.directive"; @@ -83,10 +83,11 @@ import { IconComponent } from "./vault/components/icon.component"; LinkModule, IconModule, TextDragDirective, + CopyClickDirective, + A11yTitleDirective, ], declarations: [ A11yInvalidDirective, - A11yTitleDirective, ApiActionDirective, AutofocusDirective, BoxRowDirective, @@ -105,7 +106,6 @@ import { IconComponent } from "./vault/components/icon.component"; StopClickDirective, StopPropDirective, TrueFalseValueDirective, - CopyClickDirective, LaunchClickDirective, UserNamePipe, PasswordStrengthComponent, diff --git a/libs/angular/src/directives/a11y-title.directive.ts b/libs/components/src/a11y/a11y-title.directive.ts similarity index 98% rename from libs/angular/src/directives/a11y-title.directive.ts rename to libs/components/src/a11y/a11y-title.directive.ts index f5f016b93c0..c3833f42ed2 100644 --- a/libs/angular/src/directives/a11y-title.directive.ts +++ b/libs/components/src/a11y/a11y-title.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ selector: "[appA11yTitle]", + standalone: true, }) export class A11yTitleDirective implements OnInit { @Input() set appA11yTitle(title: string) { diff --git a/libs/components/src/a11y/index.ts b/libs/components/src/a11y/index.ts new file mode 100644 index 00000000000..6090fb65d4e --- /dev/null +++ b/libs/components/src/a11y/index.ts @@ -0,0 +1 @@ +export * from "./a11y-title.directive"; diff --git a/libs/angular/src/directives/copy-click.directive.spec.ts b/libs/components/src/copy-click/copy-click.directive.spec.ts similarity index 89% rename from libs/angular/src/directives/copy-click.directive.spec.ts rename to libs/components/src/copy-click/copy-click.directive.spec.ts index 09161ee261d..eab616b141e 100644 --- a/libs/angular/src/directives/copy-click.directive.spec.ts +++ b/libs/components/src/copy-click/copy-click.directive.spec.ts @@ -3,7 +3,8 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; + +import { ToastService } from "../"; import { CopyClickDirective } from "./copy-click.directive"; @@ -20,12 +21,14 @@ import { CopyClickDirective } from "./copy-click.directive"; #toastWithLabel > `, + standalone: true, + imports: [CopyClickDirective], }) class TestCopyClickComponent { - @ViewChild("noToast") noToastButton: ElementRef; - @ViewChild("infoToast") infoToastButton: ElementRef; - @ViewChild("successToast") successToastButton: ElementRef; - @ViewChild("toastWithLabel") toastWithLabelButton: ElementRef; + @ViewChild("noToast") noToastButton!: ElementRef; + @ViewChild("infoToast") infoToastButton!: ElementRef; + @ViewChild("successToast") successToastButton!: ElementRef; + @ViewChild("toastWithLabel") toastWithLabelButton!: ElementRef; } describe("CopyClickDirective", () => { @@ -38,7 +41,7 @@ describe("CopyClickDirective", () => { showToast.mockClear(); await TestBed.configureTestingModule({ - declarations: [CopyClickDirective, TestCopyClickComponent], + imports: [TestCopyClickComponent], providers: [ { provide: I18nService, diff --git a/libs/angular/src/directives/copy-click.directive.ts b/libs/components/src/copy-click/copy-click.directive.ts similarity index 96% rename from libs/angular/src/directives/copy-click.directive.ts rename to libs/components/src/copy-click/copy-click.directive.ts index ece867c09fd..f91366360c5 100644 --- a/libs/angular/src/directives/copy-click.directive.ts +++ b/libs/components/src/copy-click/copy-click.directive.ts @@ -4,10 +4,12 @@ import { Directive, HostListener, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService, ToastVariant } from "@bitwarden/components"; + +import { ToastService, ToastVariant } from "../"; @Directive({ selector: "[appCopyClick]", + standalone: true, }) export class CopyClickDirective { private _showToast = false; diff --git a/libs/components/src/copy-click/index.ts b/libs/components/src/copy-click/index.ts new file mode 100644 index 00000000000..82b971f1f5c --- /dev/null +++ b/libs/components/src/copy-click/index.ts @@ -0,0 +1 @@ +export * from "./copy-click.directive"; diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index ed844520444..7788f4986bf 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -1,3 +1,5 @@ +export { ButtonType } from "./shared/button-like.abstraction"; +export * from "./a11y"; export * from "./async-actions"; export * from "./avatar"; export * from "./badge-list"; @@ -5,13 +7,13 @@ export * from "./badge"; export * from "./banner"; export * from "./breadcrumbs"; export * from "./button"; -export { ButtonType } from "./shared/button-like.abstraction"; export * from "./callout"; export * from "./card"; export * from "./checkbox"; export * from "./chip-select"; export * from "./color-password"; export * from "./container"; +export * from "./copy-click"; export * from "./dialog"; export * from "./disclosure"; export * from "./drawer"; diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts index 9bb96d1accd..39fcc39bf58 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts @@ -4,7 +4,6 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { CopyClickDirective } from "@bitwarden/angular/directives/copy-click.directive"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; @@ -17,7 +16,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; -import { BitFormFieldComponent, ToastService } from "@bitwarden/components"; +import { CopyClickDirective, BitFormFieldComponent, ToastService } from "@bitwarden/components"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component"; From c6a3055184d4824e6a4a6287a6f805492e16d182 Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Mon, 20 Jan 2025 16:51:57 +0100 Subject: [PATCH 250/270] [PM-14894] Remove old sales tax rates references (#12784) --- libs/common/src/abstractions/api.service.ts | 2 -- .../models/response/tax-rate.response.ts | 18 ------------------ libs/common/src/services/api.service.ts | 6 ------ 3 files changed, 26 deletions(-) delete mode 100644 libs/common/src/billing/models/response/tax-rate.response.ts diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 997974e0581..4b3dd335d15 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -95,7 +95,6 @@ import { PaymentResponse } from "../billing/models/response/payment.response"; import { PlanResponse } from "../billing/models/response/plan.response"; import { SubscriptionResponse } from "../billing/models/response/subscription.response"; import { TaxInfoResponse } from "../billing/models/response/tax-info.response"; -import { TaxRateResponse } from "../billing/models/response/tax-rate.response"; import { DeleteRecoverRequest } from "../models/request/delete-recover.request"; import { EventRequest } from "../models/request/event.request"; import { KdfRequest } from "../models/request/kdf.request"; @@ -376,7 +375,6 @@ export abstract class ApiService { ): Promise>; deleteOrganizationConnection: (id: string) => Promise; getPlans: () => Promise>; - getTaxRates: () => Promise>; getProviderUsers: (providerId: string) => Promise>; getProviderUser: (providerId: string, id: string) => Promise; diff --git a/libs/common/src/billing/models/response/tax-rate.response.ts b/libs/common/src/billing/models/response/tax-rate.response.ts deleted file mode 100644 index 2c07129ba2c..00000000000 --- a/libs/common/src/billing/models/response/tax-rate.response.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { BaseResponse } from "../../../models/response/base.response"; - -export class TaxRateResponse extends BaseResponse { - id: string; - country: string; - state: string; - postalCode: string; - rate: number; - - constructor(response: any) { - super(response); - this.id = this.getResponseProperty("Id"); - this.country = this.getResponseProperty("Country"); - this.state = this.getResponseProperty("State"); - this.postalCode = this.getResponseProperty("PostalCode"); - this.rate = this.getResponseProperty("Rate"); - } -} diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index dc0a8d61f64..7269f6e86fd 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -103,7 +103,6 @@ import { PaymentResponse } from "../billing/models/response/payment.response"; import { PlanResponse } from "../billing/models/response/plan.response"; import { SubscriptionResponse } from "../billing/models/response/subscription.response"; import { TaxInfoResponse } from "../billing/models/response/tax-info.response"; -import { TaxRateResponse } from "../billing/models/response/tax-rate.response"; import { DeviceType } from "../enums"; import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; import { CollectionBulkDeleteRequest } from "../models/request/collection-bulk-delete.request"; @@ -897,11 +896,6 @@ export class ApiService implements ApiServiceAbstraction { return new ListResponse(r, PlanResponse); } - async getTaxRates(): Promise> { - const r = await this.send("GET", "/plans/sales-tax-rates/", null, true, true); - return new ListResponse(r, TaxRateResponse); - } - // Settings APIs async getSettingsDomains(): Promise { From 33c2e09025d84ae01c1b926f010d323af1be6ffb Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:21:47 +0100 Subject: [PATCH 251/270] Autosync the updated translations (#12924) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 141 ++++++++-- apps/web/src/locales/ar/messages.json | 141 ++++++++-- apps/web/src/locales/az/messages.json | 141 ++++++++-- apps/web/src/locales/be/messages.json | 151 ++++++++-- apps/web/src/locales/bg/messages.json | 145 ++++++++-- apps/web/src/locales/bn/messages.json | 141 ++++++++-- apps/web/src/locales/bs/messages.json | 141 ++++++++-- apps/web/src/locales/ca/messages.json | 151 ++++++++-- apps/web/src/locales/cs/messages.json | 147 ++++++++-- apps/web/src/locales/cy/messages.json | 141 ++++++++-- apps/web/src/locales/da/messages.json | 149 ++++++++-- apps/web/src/locales/de/messages.json | 159 ++++++++--- apps/web/src/locales/el/messages.json | 143 ++++++++-- apps/web/src/locales/en_GB/messages.json | 135 +++++++-- apps/web/src/locales/en_IN/messages.json | 135 +++++++-- apps/web/src/locales/eo/messages.json | 141 ++++++++-- apps/web/src/locales/es/messages.json | 151 ++++++++-- apps/web/src/locales/et/messages.json | 141 ++++++++-- apps/web/src/locales/eu/messages.json | 141 ++++++++-- apps/web/src/locales/fa/messages.json | 151 ++++++++-- apps/web/src/locales/fi/messages.json | 151 ++++++++-- apps/web/src/locales/fil/messages.json | 141 ++++++++-- apps/web/src/locales/fr/messages.json | 149 ++++++++-- apps/web/src/locales/gl/messages.json | 141 ++++++++-- apps/web/src/locales/he/messages.json | 141 ++++++++-- apps/web/src/locales/hi/messages.json | 141 ++++++++-- apps/web/src/locales/hr/messages.json | 151 ++++++++-- apps/web/src/locales/hu/messages.json | 141 ++++++++-- apps/web/src/locales/id/messages.json | 141 ++++++++-- apps/web/src/locales/it/messages.json | 151 ++++++++-- apps/web/src/locales/ja/messages.json | 151 ++++++++-- apps/web/src/locales/ka/messages.json | 141 ++++++++-- apps/web/src/locales/km/messages.json | 141 ++++++++-- apps/web/src/locales/kn/messages.json | 141 ++++++++-- apps/web/src/locales/ko/messages.json | 141 ++++++++-- apps/web/src/locales/lv/messages.json | 145 ++++++++-- apps/web/src/locales/ml/messages.json | 141 ++++++++-- apps/web/src/locales/mr/messages.json | 141 ++++++++-- apps/web/src/locales/my/messages.json | 141 ++++++++-- apps/web/src/locales/nb/messages.json | 141 ++++++++-- apps/web/src/locales/ne/messages.json | 141 ++++++++-- apps/web/src/locales/nl/messages.json | 135 +++++++-- apps/web/src/locales/nn/messages.json | 141 ++++++++-- apps/web/src/locales/or/messages.json | 141 ++++++++-- apps/web/src/locales/pl/messages.json | 151 ++++++++-- apps/web/src/locales/pt_BR/messages.json | 151 ++++++++-- apps/web/src/locales/pt_PT/messages.json | 141 ++++++++-- apps/web/src/locales/ro/messages.json | 141 ++++++++-- apps/web/src/locales/ru/messages.json | 151 ++++++++-- apps/web/src/locales/si/messages.json | 141 ++++++++-- apps/web/src/locales/sk/messages.json | 139 ++++++++-- apps/web/src/locales/sl/messages.json | 141 ++++++++-- apps/web/src/locales/sr/messages.json | 151 ++++++++-- apps/web/src/locales/sr_CS/messages.json | 141 ++++++++-- apps/web/src/locales/sv/messages.json | 141 ++++++++-- apps/web/src/locales/te/messages.json | 141 ++++++++-- apps/web/src/locales/th/messages.json | 141 ++++++++-- apps/web/src/locales/tr/messages.json | 151 ++++++++-- apps/web/src/locales/uk/messages.json | 151 ++++++++-- apps/web/src/locales/vi/messages.json | 141 ++++++++-- apps/web/src/locales/zh_CN/messages.json | 153 ++++++++-- apps/web/src/locales/zh_TW/messages.json | 337 +++++++++++++++-------- 62 files changed, 7567 insertions(+), 1553 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 1dd2d62f2a5..206ff27e9ba 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 2c2549e2f1e..bc22f9e132a 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index b140ae320be..c7d98b5f41d 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Riskli üzvlər" }, + "atRiskMembersWithCount": { + "message": "Risk altındakı üzvlər ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Risk altındakı tətbiqlər ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Bu üzvlər, tətbiqlərə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edir." + }, + "atRiskApplicationsDescription": { + "message": "Bu tətbiqlər zəif, ifşa olunmuş və ya təkrar istifadə edilmiş parollara sahibdir." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Bu üzvlər, $APPNAME$ tətbiqinə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edir.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Cəmi üzv" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "1 dəvət haqqınız var." }, + "inviteZeroEmailDesc": { + "message": "0 dəvət haqqınız var." + }, "userUsingTwoStep": { "message": "Bu istifadəçinin hesabını qorumaq üçün iki addımlı giriş istifadə edilir." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Güvənli" }, + "needsApproval": { + "message": "Təsdiq lazımdır" + }, + "areYouTryingtoLogin": { + "message": "Giriş etməyə çalışırsınız?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ tərəfindən giriş cəhdi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Cihaz növü" + }, + "ipAddress": { + "message": "IP Ünvan" + }, + "confirmLogIn": { + "message": "Girişi təsdiqlə" + }, + "denyLogIn": { + "message": "Girişə rədd cavabı ver" + }, + "thisRequestIsNoLongerValid": { + "message": "Bu tələb artıq yararsızdır." + }, + "logInConfirmedForEmailOnDevice": { + "message": "$DEVICE$ cihazında $EMAIL$ üçün giriş təsdiqləndi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Başqa bir cihazdan giriş cəhdinə rədd cavabı verdiniz. Bu həqiqətən siz idinizsə, cihazla yenidən giriş etməyə çalışın." + }, + "loginRequestHasAlreadyExpired": { + "message": "Giriş tələbinin müddəti artıq bitib." + }, + "justNow": { + "message": "İndicə" + }, + "requestedXMinutesAgo": { + "message": "$MINUTES$ dəqiqə əvvəl tələb göndərildi", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Hesab yaradılır" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Güvənli cihazlar" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Kimlik doğrulandıqdan sonra üzvlər, cihazlarından saxlanılan açarı istifadə edərək seyf datasının şifrəsini aça biləcək", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "SSO ilə giriş edərkən üzvlərin ana parola ehtiyacı olmayacaq. Ana parol, cihazda saxlanılan bir şifrələmə açarı ilə əvəz olunur və bu da cihazı güvənli hala gətirir. Üzvün hesabını yaradıb giriş etdiyi ilk cihaz, güvənli hesab olunacaq. Yeni cihazların mövcud güvənli cihaz və ya bir inzibatçı tərəfindən təsdiqlənməsi lazım gələcək.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "vahid təşkilat", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "Vahid təşkilat", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "siyasəti,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO tələb olunur", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO tələb olunan", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "siyasəti və", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "siyasət və", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "hesab geri qaytarma administrasiyası", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "bu seçim istifadə edildikdə avto-yazılma ilə işə salınacaq.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "siyasəti, bu seçim istifadə olunduqda işə düşəcək.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Təşkilatınızın icazələri güncəlləndi və bir ana parol ayarlamağınızı tələb edir.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Təşkilat adı 50 xarakterdən çox ola bilməz." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Abunəliyiniz tezliklə yenilənəcək. Kəsintisiz xidməti təmin etmək və yeniləməni $RENEWAL_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Abunəliyinizə aid faktura $ISSUED_DATE$ tarixində təqdim edildi. Kəsintisiz xidməti təmin etmək və yeniləməni $DUE_DATE$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Abunəliyinizə aid faktura üzrə ödəniş edilmədi. Kəsintisiz xidməti təmin etmək və yeniləməni $GRACE_PERIOD_END$ tarixindən əvvəl təsdiqləmək üçün $RESELLER$ ilə əlaqə saxlayın.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 2a037c5b11c..03d7bf3a984 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Гэты карыстальнік выкарыстоўвае двухэтапны ўваход для абароны свайго ўліковага запісу." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Давераныя прылады" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Пасля аўтэнтыфікацыі ўдзельнікі расшыфроўваюць даныя сховішча з выкарыстаннем ключа, які захоўваецца на іх прыладах.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "адзіная арганізацыя", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "палітыка", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "патрабуецца SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "палітыка і", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "адміністраванне аднаўлення ўліковага запісу", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "палітыка з аўтаматычнай рэгістрацыяй уключаецца пры выкарыстанні гэтага параметра.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 531cfd18a42..d8edea8adf7 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -15,7 +15,7 @@ "message": "Рискова парола" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." + "message": "Прегледайте паролите в риск (слаби, преизползвани или разобличени) в различните приложения. Изберете най-важните си приложения, за да дадете приоритет на действията по сигурността за потребителите си, така че те да обърнат внимание на паролите си в риск." }, "dataLastUpdated": { "message": "Последно обновяване на данните: $DATE$", @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Членове в риск" }, + "atRiskMembersWithCount": { + "message": "Членове в риск ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Приложения в риск ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Тези членове се вписват в приложенията със слаби, преизползвани или разобличени пароли." + }, + "atRiskApplicationsDescription": { + "message": "Тези приложения имат слаби, преизползвани или разобличени пароли." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Тези членове се вписват в $APPNAME$ със слаби, преизползвани или разобличени пароли.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Общо членове" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Имате 1 оставаща покана." }, + "inviteZeroEmailDesc": { + "message": "Имате 0 оставащи покани." + }, "userUsingTwoStep": { "message": "Този потребител използва двустепенна защита за достъп." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Доверено" }, + "needsApproval": { + "message": "Изисква одобрение" + }, + "areYouTryingtoLogin": { + "message": "Опитвате се да се впишете ли?" + }, + "logInAttemptBy": { + "message": "Опит за вписване от $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Вид устройство" + }, + "ipAddress": { + "message": "IP адрес" + }, + "confirmLogIn": { + "message": "Потвърждаване на вписването" + }, + "denyLogIn": { + "message": "Отказване на вписването" + }, + "thisRequestIsNoLongerValid": { + "message": "Тази заявка вече не е активна." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Вписването за $EMAIL$ на $DEVICE$ е одобрено", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Вие отказахте опит за вписване от друго устройство. Ако това наистина сте били Вие, опитайте да се впишете от устройството отново." + }, + "loginRequestHasAlreadyExpired": { + "message": "Заявката за вписване вече е изтекла." + }, + "justNow": { + "message": "Току-що" + }, + "requestedXMinutesAgo": { + "message": "Заявено преди $MINUTES$ минути", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Създаване на регистрация в" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Доверени устройства" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "След вписване, членовете ще дешифрират данните от трезорите си чрез ключ, който се съхранява на устройство. Политиката за", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Членовете няма да имат нужда от главна парола, когато се вписват чрез еднократно удостоверяване. Главната парола се заменя от шифроващ ключ съхраняван на устройството, което прави това устройство доверено. Първото устройство, с което членът създаде акаунта си и на което се впише, ще бъде доверено. Новите устройства ще трябва да бъдат одобрени от съществуващо доверено устройство или от администратор. Политиката за", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "единствена организация", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ",", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": ", политиката за", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "политиката за изискване на еднократно удостоверяване", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "еднократно удостоверяване", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "и", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "и политиката за", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "политиката за управление на възстановяването на регистрации", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "администриране на възстановяването на акаунти", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "с автоматично включване ще бъдат активирани при използването на тази настройка.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "ще бъдат включени, когато се ползва тази опция.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Правата Ви в организацията бяха променени, необходимо е да зададете главна парола.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Името на организацията не може да бъде по-дълго от 50 знака." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Вашият абонамент ще бъде подновен скоро. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Фактура за абонамента Ви беше издадена на $ISSUED_DATE$. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $DUE_DATE$.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Фактурата за абонамента Ви не е била платена. За да подсигурите, че услугата няма да има прекъсвания, свържете се с $RESELLER$ и потвърдете подновяването преди $GRACE_PERIOD_END$.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index b215e56e19a..1fccb7fc32e 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 5f2605e71bc..01d11af8e1e 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 30649f3296d..b87c098363f 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Aquest usuari fa servir l'inici de sessió en dues passes per protegir el seu compte." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Dispositius de confiança" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Una vegada autenticats, els membres desxifraran les dades de la caixa forta mitjançant una clau emmagatzemada al seu dispositiu. La", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "política d'organització", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "única,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "la política de", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "requerida y la política", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "d'administració de recuperació del compte", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "amb inscripció automàtica s'activaran quan s'utilitze aquesta opció.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Els permisos de la vostra organització s'han actualitzat, cal que establiu una contrasenya mestra.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 7c8978fac80..40edce811aa 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -113,11 +113,44 @@ "atRiskMembers": { "message": "Ohrožení členové" }, + "atRiskMembersWithCount": { + "message": "Rizikoví členové ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Rizikové aplikace ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Tito členové se přihlašují do aplikací se slabými, odhalenými nebo znovu použitými hesly." + }, + "atRiskApplicationsDescription": { + "message": "Tyto aplikace mají slabá, odhalená nebo opakovaně používaná hesla." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Tito členové se přihlašují do $APPNAME$ se slabými, odhalenými nebo znovu použitými hesly.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Celkem členů" }, "atRiskApplications": { - "message": "Ohrožené aplikace" + "message": "Rizikové aplikace" }, "totalApplications": { "message": "Celkem aplikací" @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Zbývá Vám 1 pozvánka." }, + "inviteZeroEmailDesc": { + "message": "Zbývá Vám 0 pozvánek." + }, "userUsingTwoStep": { "message": "Tento uživatel používá pro ochranu svého účtu dvoufázové přihlášení." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Důvěryhodný" }, + "needsApproval": { + "message": "Vyžaduje schválení" + }, + "areYouTryingtoLogin": { + "message": "Pokoušíte se přihlásit?" + }, + "logInAttemptBy": { + "message": "Pokus o přihlášení z $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Typ zařízení" + }, + "ipAddress": { + "message": "IP adresa" + }, + "confirmLogIn": { + "message": "Potvrdit přihlášení" + }, + "denyLogIn": { + "message": "Zamítnout přihlášení" + }, + "thisRequestIsNoLongerValid": { + "message": "Tento požadavek již není platný." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Přihlášení bylo potvrzeno z $EMAIL$ pro $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Pokus o přihlášení byl zamítnut z jiného zařízení. Pokud jste to opravdu Vy, zkuste se znovu přihlásit do zařízení." + }, + "loginRequestHasAlreadyExpired": { + "message": "Požadavek na přihlášení již vypršel." + }, + "justNow": { + "message": "Právě teď" + }, + "requestedXMinutesAgo": { + "message": "Požadováno před $MINUTES$ minutami", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Vytváření účtu na" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Důvěryhodná zařízení" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Po ověření budou členové dešifrovat data v trezoru pomocí klíče uloženého na jejich zařízení. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "Při použití této možnosti ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "se zapnou zásady jednotné organizace, ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "zásady vyžadované SSO ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "a zásady správy obnovení účtu ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "s ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "s automatickým zápisem.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Oprávnění Vaší organizace byla aktualizována. To vyžaduje nastavení hlavního hesla.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Název organizace nesmí přesáhnout 50 znaků." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Vaše předplatné se brzy obnoví. Chcete-li zajistit nepřerušenou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Faktura pro Vaše předplatné byla vystavena dne $ISSUED_DATE$. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $DUE_DATE$.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Faktura za Vaše předplatné nebyla zaplacena. Chcete-li zajistit nepřerušovanou službu, kontaktujte $RESELLER$ pro potvrzení obnovení před $GRACE_PERIOD_END$.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index ed8a03eb8b0..9e08bcd173e 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 9731c344e6e..5759e59ee1c 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Udsatte medlemmer" }, + "atRiskMembersWithCount": { + "message": "Udsatte medlemmer ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Udsatte applikationer ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Disse medlemmer logger ind på programmer med svage, udsatte eller genbrugte adgangskoder." + }, + "atRiskApplicationsDescription": { + "message": "Disse applikationer har svage, udsatte eller genbrugte adgangskoder." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Disse medlemmer logger ind på $APPNAME$ med svage, udsatte eller genbrugte adgangskoder.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Medlemmer i alt" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Der er 1 invitation tilbage." }, + "inviteZeroEmailDesc": { + "message": "Der er 0 invitationer tilbage." + }, "userUsingTwoStep": { "message": "Denne bruger benytter totrins-login for at beskytte kontoen." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Betroet" }, + "needsApproval": { + "message": "Kræver godkendelse" + }, + "areYouTryingtoLogin": { + "message": "Forsøger at logge ind?" + }, + "logInAttemptBy": { + "message": "Loginforsøg fra $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Enhedstype" + }, + "ipAddress": { + "message": "IP-adresse" + }, + "confirmLogIn": { + "message": "Bekræft login" + }, + "denyLogIn": { + "message": "Afvis login" + }, + "thisRequestIsNoLongerValid": { + "message": "Anmodningen er ikke længere gyldig." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login bekræftet for $EMAIL$ på $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Du nægtede et loginforsøg fra en anden enhed. Hvis dette virkelig var dig, prøv at logge ind med enheden igen." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login-anmodning er allerede udløbet." + }, + "justNow": { + "message": "Netop nu" + }, + "requestedXMinutesAgo": { + "message": "Anmodet for $MINUTES$ minutter siden", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Opretter konto på" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Betroede enheder" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Når godkendt, dekrypterer medlemmet boksdata vha. en nøgle gemt på deres enhed. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Medlemmer behøver ikke en hovedadgangskode ved indlogning med SSO. Hovedadgangskoden erstattes af en krypteringsnøgle gemt på enheden, hvilket gør enheden betroet. Den første enhed, hvorfra et medlem opretter sin konto og logger ind, vil være betroet. Nye enheder skal godkendes af en eksisterende betroet enhed eller af en administrator. Den", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "Den enkelte organisations", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "enkelte organisations", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "politik,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO-påkrævede", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO-krævet", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "politik samt", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "politik og", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "kontogendannelseshåndteringspolitik", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "kontogendannelseshåndterings-", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "med automatisk indrullering slås til ved brug af denne indstilling.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "politik træder i kraft, når denne indstilling bruges.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Organisationstilladelserne er blevet opdateret, og der kræves nu oprettelse af en hovedadgangskode.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organisationsnavn må ikke overstige 50 tegn." }, - "resellerRenewalWarning": { - "message": "Abonnementet fornyes snart. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Abonnementet fornyes snart. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ inden $RENEWAL_DATE$ for at bekræfte fornyelsen.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "En faktura for abonnementet er udstedt pr. $ISSUED_DATE$. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "En abonnementsfaktura er udstedt pr. $ISSUED_DATE$. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ inden $DUE_DATE$ for at bekræfte fornyelsen.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "Fakturaen for abonnementet er ikke blevet betalt. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ for at bekræfte fornyelsen inden $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "Abonnementsfakturaen er ikke blevet betalt. For at sikre uafbrudt tjeneste, kontakt $RESELLER$ inden $GRACE_PERIOD_END$ for at bekræfte fornyelsen.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 540db2dec05..2977a681004 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Risikoreiche Mitglieder" }, + "atRiskMembersWithCount": { + "message": "Risikoreiche Mitglieder ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Risikoreiche Anwendungen ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Diese Mitglieder melden sich bei Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an." + }, + "atRiskApplicationsDescription": { + "message": "Diese Anwendungen haben schwache, kompromittierte oder wiederverwendete Passwörter." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Diese Mitglieder melden sich bei $APPNAME$ mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Mitglieder insgesamt" }, @@ -1129,10 +1162,10 @@ "message": "Verifiziere deine Identität" }, "whatIsADevice": { - "message": "Was ist ein \"Gerät\"?" + "message": "Was ist ein Gerät?" }, "aDeviceIs": { - "message": "Ein \"Gerät\" ist eine einzigartige Installation der Bitwarden-App, in der du dich angemeldet hast. Eine Neuinstallation, das Löschen von App-Daten oder das Löschen von Cookies könnte dazu führen, dass ein \"Gerät\" mehrfach erscheint." + "message": "Ein Gerät ist eine einzigartige Installation der Bitwarden-App, in der du dich angemeldet hast. Eine Neuinstallation, das Löschen von App-Daten oder das Löschen von Cookies könnte dazu führen, dass ein Gerät mehrfach erscheint." }, "logInInitiated": { "message": "Anmeldung eingeleitet" @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Du hast noch eine Einladung übrig." }, + "inviteZeroEmailDesc": { + "message": "Du hast 0 Einladungen übrig." + }, "userUsingTwoStep": { "message": "Dieser Benutzer hat sein Konto mit einer Zwei-Faktor-Authentifizierung geschützt." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Vertrauenswürdig" }, + "needsApproval": { + "message": "Benötigt Genehmigung" + }, + "areYouTryingtoLogin": { + "message": "Versuchst du dich anzumelden?" + }, + "logInAttemptBy": { + "message": "Anmeldeversuch von $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Gerätetyp" + }, + "ipAddress": { + "message": "IP-Adresse" + }, + "confirmLogIn": { + "message": "Anmeldung genehmigen" + }, + "denyLogIn": { + "message": "Anmeldung ablehnen" + }, + "thisRequestIsNoLongerValid": { + "message": "Diese Anfrage ist nicht mehr gültig." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Anmeldung von $EMAIL$ auf $DEVICE$ bestätigt", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Du hast einen Anmeldeversuch von einem anderen Gerät abgelehnt. Wenn du das wirklich warst, versuche dich erneut mit dem Gerät anzumelden." + }, + "loginRequestHasAlreadyExpired": { + "message": "Anmeldeanfrage ist bereits abgelaufen." + }, + "justNow": { + "message": "Gerade eben" + }, + "requestedXMinutesAgo": { + "message": "Vor $MINUTES$ Minuten angefordert", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Konto wird erstellt bei" }, @@ -5686,11 +5783,11 @@ "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." }, "contactCSToAvoidDataLossPart1": { - "message": "Kontaktiere das Kundenerfolgsteam", + "message": "Kontaktiere den Kundensupport", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "um zusätzlichen Datenverlust zu vermeiden.", + "message": ", um zusätzlichen Datenverlust zu vermeiden.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "accountRecoveryManageUsers": { @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Vertrauenswürdige Geräte" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Einmal authentifiziert, entschlüsseln Mitglieder Tresordaten mit einem Schlüssel auf ihrem Gerät. Die Richtlinie für", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Mitglieder benötigen kein Master-Passwort beim Anmelden mit SSO. Das Master-Passwort wird durch einen auf dem Gerät gespeicherten Verschlüsselungsschlüssel ersetzt, wodurch das Gerät vertrauenswürdig ist. Dem erste Gerät, auf das ein Mitglied sein Konto erstellt und sich anmeldet, wird vertraut. Neue Geräte müssen von einem bestehenden vertrauenswürdigen Gerät oder einem Administrator genehmigt werden. Die Richtlinie für eine", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "einzelne Organisationen", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "einzelne Organisation", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": ", die Richtlinie zum", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "Verlangen einer SSO-Authentifizierung", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "und die Richtlinie für die", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "Kontowiederherstellungsverwaltung", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "mit automatischer Registrierung werden aktiviert, wenn diese Option verwendet wird.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "werden aktiviert, wenn diese Option verwendet wird.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Deine Organisationsberechtigungen wurden aktualisiert und verlangen, dass du ein Master-Passwort festlegen musst.", @@ -8275,16 +8372,16 @@ "message": "Anfrage genehmigen" }, "deviceApproved": { - "message": "\"Gerät\" genehmigt" + "message": "Gerät genehmigt" }, "deviceRemoved": { - "message": "\"Gerät\" entfernt" + "message": "Gerät entfernt" }, "removeDevice": { - "message": "\"Gerät\" entfernen" + "message": "Gerät entfernen" }, "removeDeviceConfirmation": { - "message": "Möchtest du das \"Gerät\" wirklich entfernen?" + "message": "Bist du sicher, das du dieses Gerät entfernen möchtest?" }, "noDeviceRequests": { "message": "Keine Geräteanfragen" @@ -9986,13 +10083,13 @@ "message": "Mitglieder entfernen" }, "devices": { - "message": "\"Geräte\"" + "message": "Geräte" }, "deviceListDescription": { - "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt. Wenn du ein \"Gerät\" nicht wiedererkennst, dann entferne es jetzt." + "message": "Dein Konto ist bei jedem der folgenden Geräte angemeldet. Wenn du ein Gerät nicht wiedererkennst, entferne es jetzt." }, "deviceListDescriptionTemp": { - "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt." + "message": "Dein Konto wurde bei jedem der unter aufgeführten Geräte angemeldet." }, "claimedDomains": { "message": "Beanspruchte Domains" @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Der Name der Organisation darf 50 Zeichen nicht überschreiten." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Dein Abonnement wird bald verlängert. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$ um deine Verlängerung vor dem $RENEWAL_DATE$ zu bestätigen.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Eine Rechnung für dein Abonnement wurde am $ISSUED_DATE$ ausgestellt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $DUE_DATE$ zu bestätigen.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Die Rechnung für dein Abonnement wurde nicht bezahlt. Um einen ununterbrochenen Betrieb zu gewährleisten, kontaktiere $RESELLER$, um deine Verlängerung vor dem $GRACE_PERIOD_END$ zu bestätigen.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index adfb86665ab..218d72c3e58 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Σύνολο μελών" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Αυτός ο χρήστης χρησιμοποιεί τρόπο σύνδεσης δύο βημάτων για να προστατεύσει το λογαριασμό του." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Δημιουργία λογαριασμού στο" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Έμπιστες συσκευές" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "Απαιτείται SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index a2002039ab9..ece3d75e638 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device from where a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organisation", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrolment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organisation permissions were updated, requiring you to set a master password.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Organisation name cannot exceed 50 characters." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 2564812802a..d9263876859 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device from where a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organisation", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organisation permissions were updated, requiring you to set a master password.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Organisation name cannot exceed 50 characters." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 21eba5a4a99..c3f767d0507 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Ĉi tiu uzanto uzas du-paŝan ensaluton por protekti sian konton." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 1393fe59b27..850eb82897c 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Este usuario está usando autenticación de dos pasos para proteger su cuenta." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creando una cuenta en" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Dispositivos de confianza" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "organización única", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "política,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO requerido", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "política, y", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "administración de recuperación de cuenta", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 9122e4b8778..d58b2ab26d4 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Sellel kasutajal on kaheastmeline kinnitamine sisse lülitatud." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Konto loomise asukoht" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 576d8207aae..666bd261d5a 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Erabiltzaile hau bi urratseko saio hasiera erabiltzen ari da bere kontua babesteko." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 589a019973f..c7e6486cd67 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "این کاربر از ورود دو مرحله ای برای محافظت از حساب خود استفاده می‌کند." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "دستگاه‌های مورد اعتماد" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "پس از احراز هویت، اعضا با استفاده از کلید ذخیره شده در دستگاه خود، داده‌های گاوصندوق را رمزگشایی می‌کنند.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "سازمان واحد", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "سیاست‌ها", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO الزامی است", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "سیاست‌ها و", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "مدیریت بازیابی حساب کاربری", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "سیاست‌ها با ثبت نام خودکار زمانی که از این گزینه استفاده می‌شود روشن می‌شود.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index b6825bed162..6a30c6d08da 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Riskialttiit jäsenet" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Jäseniä yhteensä" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Käyttäjä on suojannut tilinsä kaksivaiheisella kirjautumisella." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Luodaan tili palvelimelle" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Luotetut laitteet" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Kun jäsenet ovat tunnistautuneet, he voivat purkaa holvin salauksen omalla laitteellaan säilytettävällä avaimella.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "\"Yksittäinen organisaatio\"", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "ja", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "\"Kertakirjautuminen vaaditaan\"", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "-käytännöt sekä", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "\"Tilien palautusavun hallinta\"", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "-käytäntö automaattisella liitoksella otetaan käyttöön tämän valinnan käyttöönoton yhteydessä.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Organisaatiosi käyttöoikeuksia muutettiin ja tämän seurauksena sinun on asetettava pääsalasana.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 9dcacdb44f8..55574b772ad 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Gumagamit ang user na ito ng dalawang hakbang na pag login upang maprotektahan ang kanilang account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 933bfd8759f..869d39ae472 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Membres à risque" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Applications à risque ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "Ces applications ont des mots de passe faibles, exposés ou réutilisés." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total des membres" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "Il vous reste 0 invitations." + }, "userUsingTwoStep": { "message": "Cet utilisateur utilise l'authentification à deux facteurs pour protéger son compte." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Approuvé" }, + "needsApproval": { + "message": "Requiert une approbation" + }, + "areYouTryingtoLogin": { + "message": "Essayez-vous de vous connecter ?" + }, + "logInAttemptBy": { + "message": "Tentative de connexion par $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Type d'appareil" + }, + "ipAddress": { + "message": "Adresse IP" + }, + "confirmLogIn": { + "message": "Confirmer la connexion" + }, + "denyLogIn": { + "message": "Refuser la connexion" + }, + "thisRequestIsNoLongerValid": { + "message": "Cette demande n'est plus valide." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Connexion confirmée pour $EMAIL$ sur $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Vous avez refusé une tentative de connexion depuis un autre appareil. Si c'était vraiment vous, essayez de vous connecter à nouveau avec l'appareil." + }, + "loginRequestHasAlreadyExpired": { + "message": "La demande de connexion a déjà expiré." + }, + "justNow": { + "message": "À l’instant" + }, + "requestedXMinutesAgo": { + "message": "Demandé il y a $MINUTES$ minutes", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Création du compte sur" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Appareils de confiance" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Une fois authentifiés, les membres déchiffreront les données du coffre à l'aide d'une clé enregistrée sur leur appareil. La", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Les membres n'auront pas besoin d'un mot de passe maître lors de la connexion avec SSO. Le mot de passe principal est remplacé par une clé de chiffrement stockée sur le périphérique, ce qui rend ce périphérique fiable. Le premier appareil avec lequel un membre créera son compte et se connectera sera fiable. Les nouveaux appareils devront être approuvés par un périphérique de confiance existant ou par un administrateur. La", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "d'organisation unique,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" - }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescLink1": { "message": "politique", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO requise et", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "d'organisation unique,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescLink2": { "message": "la politique", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "la politique d'administration de la restauration du compte", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "SSO requise et", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "avec inscription automatique sera activée quand cette option est utilisée.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "la politique", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" + }, + "memberDecryptionOptionTdeDescPart4": { + "message": "d'administration de récupération de compte seront activées si cette option est utilisée.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Les autorisations de votre organisation ont été mises à jour, vous obligeant à définir un mot de passe principal.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Le nom de l'organisation ne doit pas dépasser 50 caractères." }, - "resellerRenewalWarning": { - "message": "Votre abonnement sera renouvelé bientôt. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Votre abonnement sera renouvelé bientôt. Pour assurer un service ininterrompu, contactez $RESELLER$ pour confirmer votre renouvellement avant le $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "Une facture pour votre abonnement a été émise sur $ISSUED_DATE$. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "Une facture pour votre abonnement a été émise le $ISSUED_DATE$. Pour assurer un service ininterrompu, contactez $RESELLER$ pour confirmer votre renouvellement avant le $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "La facture de votre abonnement n'a pas été payée. Pour assurer un service sans interruption, contactez $RESELLER$ pour confirmer votre renouvellement avant $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "La facture de votre abonnement n'a pas été payée. Pour assurer un service ininterrompu, contactez $RESELLER$ pour confirmer votre renouvellement avant $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 3e2c1161067..8ad001fa632 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 6db8255c46e..f90c42a417e 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "משתמש זה הפעיל כניסה דו שלבית כדי להגן על חשבונו." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 25f7bc6e6f7..ce3661942cd 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 610b63d68c1..f0f53ef2311 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Rizični korisnici" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Ukupno korisnika" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Ovaj korisnik upotrebljava prijavu u dva koraka za zaštitu svog računa." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Stvaranje računa na" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Pouzdani uređaji" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Jednom autentificirani, korisnici će moći dešifrirati podatke u trezoru koristeći ključ spremljen na njihovom uređaju. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "pravilo Isključive organizacije", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ",", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "pravilo obavezne SSO autentifikacije", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "pravilo i ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "pravilo administracije povrata računa", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "s automatskim učlanjenjem će se uključiti kada se koristi ova opcija.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Moraš postaviti glavnu lozinku jer su dopuštenja tvoje organizacije ažurirana.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Naziv organizacije ne može biti duži od 50 znakova." }, - "resellerRenewalWarning": { - "message": "Tvoja će se pretplata uskoro obnoviti. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "Za tvoju je pretplatu $ISSUED_DATE$ izdana faktura. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnove prije $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "Faktura za tvoju pretplatu nije plaćena. Za neprekinutu uslugu, kontaktiraj $RESELLER$ za potvrdu obnovu prije $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 163355b20d9..57520933201 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Veszélyes tagok" }, + "atRiskMembersWithCount": { + "message": "Veszélyes tagok ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Kockázatos alkalmazások ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Ezek a tagok gyenge, nyílt vagy újrafelhasznált jelszavakkal jelentkeznek be az alkalmazásokba." + }, + "atRiskApplicationsDescription": { + "message": "Ezeknél az alkalmazásoknál gyenge, nyílt vagy újra felhasznált jelszavak vannak." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Ezek a tagok gyenge, nyilvános vagy újrahasznált jelszavakkal jelentkeznek be $APPNAME$ alkalmazásba.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Összes tagok száma" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "1 meghívó maradt." }, + "inviteZeroEmailDesc": { + "message": "0 meghívó maradt." + }, "userUsingTwoStep": { "message": "Ez a felhasználó kétlépcsős bejelentkezést használ fiókja védelmére." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Megbízható" }, + "needsApproval": { + "message": "Jóváhagyást igényel" + }, + "areYouTryingtoLogin": { + "message": "Megpróbálunk bejelentkezni?" + }, + "logInAttemptBy": { + "message": "Bejelentkezési kísérlet $EMAIL$ segítségével", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Eszköz típus" + }, + "ipAddress": { + "message": "IP cím" + }, + "confirmLogIn": { + "message": "Bejelentkezés megerősítése" + }, + "denyLogIn": { + "message": "Bejelentkezés megtagadása" + }, + "thisRequestIsNoLongerValid": { + "message": "A kérés a továbbiakban már nem érvényes." + }, + "logInConfirmedForEmailOnDevice": { + "message": "A bejelelentketés $EMAIL$ email címmel megerősítésre került $DEVICE$ eszközön.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Megtagadásra került egy bejelentkezési kísérletet egy másik eszközről. Ha valóban mi voltunk, próbáljunk meg újra bejelentkezni az eszközzel." + }, + "loginRequestHasAlreadyExpired": { + "message": "A bejelentkezési kérés már lejárt." + }, + "justNow": { + "message": "Éppen most" + }, + "requestedXMinutesAgo": { + "message": "Kérve $MINUTES$ perccel ezelőtt", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Fiók létrehozása:" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Megbízható eszközök" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "A hitelesítés után a tagok az eszközükön tárolt kulccsal visszafejtik a tároló adatait. Ehhez", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "A tagoknak nincs szükségük mesterjelszóra az egyszeri bejelentkezéskor. A mesterjelszót az eszközön tárolt titkosítási kulccsal helyettesítjük, így az eszköz megbízható. Az az első eszköz, amelyen a tag létrehozza a fiókját és bejelentkezik, megbízható lesz. Az új eszközöket egy meglévő megbízható eszköznek vagy egy rendszergazdának kell jóváhagynia. Az", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "önálló szervezet", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "rendszabály,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO szükséges", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "az SSO szükséges", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "rendszabály és ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "rendszabály és", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "fiók helyreállítási adminisztráció", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "a fiók helyreállítási adminisztráció", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "automatikus regisztrációval rendelkező rendszabály kerül bekapcsolásra, ha ezt az opció van használatban.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "rendszabály bekapcsolásra kerül, ha ezt a lehetőséget használjuk.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "A szervezeti engedélyek frissítésre kerültek, ezért be kell állítani egy mesterjelszót.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "A szervezet neve nem haladhatja meg az 50 karaktert." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Az előfizetés hamarosan megújul. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $RENEWAL_DATE$ előtt.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Az előfizetésről szóló számla kiállítása $ISSUED_DATE$ napon történt. A megszakítás nélküli szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $DUE_DATE$ előtt.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Az előfizetésről szóló számla nem lett kfizetve. A folyamatos szolgáltatás biztosítása érdekében lépjünk kapcsolatba $RESELLER$ viszonteladóval és erősítsük meg a megújítást $GRACE_PERIOD_END$ előtt.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 3b861fb16da..5e6d6570a44 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Pengguna ini menggunakan proses masuk dua langkah untuk melindungi akun mereka." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 8ce01a4737d..b6a39905add 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "Membri a rischio ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Applicazioni a rischio ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Questi membri accedono ad applicazioni con parole d'accesso deboli, esposte, o riutilizzate." + }, + "atRiskApplicationsDescription": { + "message": "Queste applicazioni hanno parole d'accesso deboli, esposte o riutilizzate." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Questi membri stanno entrando in $APPNAME$ con parole d'accesso deboli, esposte, o riutilizzate.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Hai ancora 1 invito." }, + "inviteZeroEmailDesc": { + "message": "Hai 0 inviti rimanenti." + }, "userUsingTwoStep": { "message": "Questo utente usa la verifica in due passaggi per proteggere il suo account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Di fiducia" }, + "needsApproval": { + "message": "Necessita approvazione" + }, + "areYouTryingtoLogin": { + "message": "Stai tentando di accedere?" + }, + "logInAttemptBy": { + "message": "Tentativo di accesso di $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Tipo di dispositivo" + }, + "ipAddress": { + "message": "Indirizzo IP" + }, + "confirmLogIn": { + "message": "Conferma accesso" + }, + "denyLogIn": { + "message": "Nega accesso" + }, + "thisRequestIsNoLongerValid": { + "message": "La richiesta non è più valida." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Accesso confermato per $EMAIL$ con $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Hai negato un tentativo di accesso da un altro dispositivo. Se eri davvero tu, prova di nuovo ad accedere con il dispositivo." + }, + "loginRequestHasAlreadyExpired": { + "message": "La richiesta di accesso è già scaduta." + }, + "justNow": { + "message": "Proprio ora" + }, + "requestedXMinutesAgo": { + "message": "Richiesto $MINUTES$ minuti fa", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creazione account su" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Dispositivi fidati" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Una volta autenticati, i membri decrittograferanno i dati della cassaforte usando una chiave memorizzata sul proprio dispositivo. La", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "I membri non avranno bisogno di una parola d'accesso principale durante l'accesso con SSO. La parola d'accesso principale è rimpiazzata da una chiave di crittografia memorizzata nel dispositivo, rendendo il dispositivo attendibile. Il primo dispositivo in cui un membro crea i propri account e accesso sarà attendibile. I nuovi dispositivi dovranno essere approvati da un dispositivo fidato esistente o da un amministratore. La", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "politica organizzazione unica", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ", la", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "politica SSO obbligatorio", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": ", e la", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "politica amministrazione del recupero dell'account", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "con registrazione automatica si attiverà quando questa opzione è usata.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Le autorizzazioni della tua organizzazione sono state aggiornate, obbligandoti a impostare una password principale.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 44213d143aa..ad8310b98a2 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "リスクがあるメンバー" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "合計メンバー数" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "このユーザーはアカウントを保護するため二段階認証を利用しています。" }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "アカウント作成:" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "信頼できるデバイス" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "認証されると、メンバーはデバイスに保存されたキーを使って保管庫のデータを復号します。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "単一組織", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "ポリシー", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO 必須", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "ポリシーと", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "アカウント回復の管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "自動登録のポリシーがオンになります。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "組織の権限が更新され、マスターパスワードの設定が必要になりました。", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 610bc0f6d87..8f44a4ca065 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index e6cce3405d5..19f8bb82e54 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 215bde966ad..cc19c636a09 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "ಈ ಬಳಕೆದಾರರು ತಮ್ಮ ಖಾತೆಯನ್ನು ರಕ್ಷಿಸಲು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಬಳಸುತ್ತಿದ್ದಾರೆ." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 0cd1302ea2f..5c06e0a2b13 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "이 사용자는 계정을 보호하기 위해 2단계 로그인을 사용하고 있습니다." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 51ca6a0f798..29d555004b8 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Riskam pakļautie dalībnieki" }, + "atRiskMembersWithCount": { + "message": "Riskam pakļautie dalībnieki ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Riskam pakļautas lietotnes ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Šie dalībnieki piesakās lietotnēs ar vājām, atklātām vai atkārtoti izmantotām parolēm." + }, + "atRiskApplicationsDescription": { + "message": "Šīm lietotnēm ir vājas, atklātas vai atkārtoti izmantotas paroles." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Šie dalībnieki piesakās $APPNAME$ ar vājām, atklātām vai atkārtoti izmantotām parolēm.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Kopējais dalībnieku skaits" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Tev ir atlicis 1 uzaicinājums." }, + "inviteZeroEmailDesc": { + "message": "Tev ir atlikuši 0 uzaicinājumu." + }, "userUsingTwoStep": { "message": "Šis lietotājs izmanto divpakāpju pieteikšanos, lai aizsargātu savu kontu." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Uzticama" }, + "needsApproval": { + "message": "Nepieciešams apstiprinājums" + }, + "areYouTryingtoLogin": { + "message": "Vai mēģini pieteikties?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ pieteikšanās mēģinājums", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Ierīces veids" + }, + "ipAddress": { + "message": "IP adrese" + }, + "confirmLogIn": { + "message": "Apstiprināt pieteikšanos" + }, + "denyLogIn": { + "message": "Atteikt pieteikšanos" + }, + "thisRequestIsNoLongerValid": { + "message": "Šis pieprasījums vairs nav derīgs." + }, + "logInConfirmedForEmailOnDevice": { + "message": "$EMAIL$ pieteikšanās apstiprināta ierīcē $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Tu noraidīji pieteikšanās mēģinājumu no citas ierīces. Ja tas tiešām biji Tu, mēģini pieteikties no ierīces vēlreiz!" + }, + "loginRequestHasAlreadyExpired": { + "message": "Pieteikšanās pieprasījuma derīgums jau ir beidzies." + }, + "justNow": { + "message": "Tikko" + }, + "requestedXMinutesAgo": { + "message": "Pieprasīts pirms $MINUTES$ minūtēm", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Tiek veidots konts" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Uzticamās ierīces" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Pēc pieteikšanās dalībnieki atšifrēs glabātavas saturu ar ierīcē glabātu atslēgu. ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "Vienas apvienības", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "nosacījums,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO nepieciešamības", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "nosacījums un", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "kontu atkopšanas pārvaldības", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "nosacījums ar automātisku ievietošanu sarakstā tiks ieslēgts, kad šī iespēja tiek izmantota.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Apvienības atļaujas tika atjauninātas, un tās pieprasa iestatīt galveno paroli.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Apvienības nosaukums nevar pārsniegt 50 rakstzīmes." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Abonements drīz tiks atjaunots. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $RENEWAL_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Rēķins par abonementu tika izdots $ISSUED_DATE$. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $DUE_DATE$ jāsazinās ar $RESELLER$, lai apstiprinātu atjaunošanu.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Rēķins par abonementu nav apmaksāts. Lai nodrošinātu nepārtrauktu pakalpojumu, pirms $GRACE_PERIOD_END$ jāsazināš ar $RESELLER$, lai apstiprinātu atjaunošanu.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index a6eb0e474fd..cd01e892e7e 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "ഈ ഉപയോക്താവ് അവരുടെ അക്കൗണ്ട് രണ്ട്-പ്രവേശനം ഉപയോഗിച്ച് സുരക്ഷിതമാക്കിയിരിക്കുന്നു." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index e6cce3405d5..19f8bb82e54 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index e6cce3405d5..19f8bb82e54 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index da80546c9a5..37c1c5224f5 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Denne brukeren bruker 2-trinnsinnlogging til å beskytte kontoen sin." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 7ebca5938e0..2e95d72a392 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index b38e6f4c3c3..c16534c7ce4 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Leden in gevaar" }, + "atRiskMembersWithCount": { + "message": "Leden die risico lopen ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Applicaties die risico lopen ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Deze leden loggen in op toepassingen met zwakke, blootgestelde of hergebruikte wachtwoorden." + }, + "atRiskApplicationsDescription": { + "message": "Deze applicaties hebben zwakke, blootgelegde of hergebruikte wachtwoorden." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Deze leden loggen in op $APPNAME$ met zwakke, blootgestelde of hergebruikte wachtwoorden.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Totaal leden" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Je hebt 1 uitnodiging over." }, + "inviteZeroEmailDesc": { + "message": "Je hebt 0 uitnodigingen over." + }, "userUsingTwoStep": { "message": "Het account van deze gebruiker is beschermd met tweestapsaanmelding." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Vertrouwd" }, + "needsApproval": { + "message": "Heeft goedkeuring nodig" + }, + "areYouTryingtoLogin": { + "message": "Probeer je in te loggen?" + }, + "logInAttemptBy": { + "message": "Inlogpoging door $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Apparaattype" + }, + "ipAddress": { + "message": "IP-adres" + }, + "confirmLogIn": { + "message": "Inloggen bevestigen" + }, + "denyLogIn": { + "message": "Inloggen afwijzen" + }, + "thisRequestIsNoLongerValid": { + "message": "Dit verzoek is niet langer geldig." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Inloggen voor $EMAIL$ bevestigd op $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Je hebt een inlogpoging vanaf een ander apparaat geweigerd. Als je dit toch echt zelf was, probeer dan opnieuw in te loggen met het apparaat." + }, + "loginRequestHasAlreadyExpired": { + "message": "Inlogverzoek is al verlopen." + }, + "justNow": { + "message": "Zojuist" + }, + "requestedXMinutesAgo": { + "message": "$MINUTES$ minuten geleden aangevraagd", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Account maken bij" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Vertrouwde apparaten" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Eenmaal ingelogd, ontsleutelen leden kluisgegevens met een op hun apparaat opgeslagen sleutel. Het", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Leden hebben geen hoofdwachtwoord nodig bij het inloggen met SSO. Het hoofdwachtwoord wordt vervangen door een encryptiesleutel die is opgeslagen op het apparaat, waardoor het apparaat is vertrouwd. Het eerste apparaat waarop een lid zijn/haar account aanmaakt en mee inlogt wordt vertrouwd. Nieuwe apparaten moeten worden goedgekeurd door een bestaand vertrouwde apparaat of door een beheerder. De", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "enkele organisatie", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "beleid,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO vereist", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "beleid en", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "accountherstel-administratie", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "beleid met automatische inschrijving wordt ingeschakeld wanneer je deze optie gebruikt.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "beleid zal worden ingeschakeld wanneer deze optie wordt gebruikt.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "De organisatierechten zijn bijgewerkt, je moet een hoofdwachtwoord instellen.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Organisatienaam mag niet langer zijn dan 50 tekens." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Je abonnement wordt binnenkort verlengd. Neem voor $RENEWAL_DATE$ contact op met $RESELLER$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Er is een factuur voor je abonnement aangemaakt op $ISSUED_DATE$. Neem contact op met $RESELLER$ voor $DUE_DATE$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "De factuur voor je abonnement is niet betaald. Neem contact op met $RESELLER$ voor $GRACE_PERIOD_END$ om je verlenging te bevestigen en een ononderbroken service te verzekeren.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index cb6d070485d..1e8d1191ee1 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index e6cce3405d5..19f8bb82e54 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index d063fa818a7..63a2d8801c3 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Ten użytkownik korzysta z logowania dwustopniowego, aby chronić swoje konto." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Tworzenie konta na" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Zaufane urządzenia" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Po uwierzytelnieniu członkowie odszyfrowają dane sejfu przy użyciu klucza zapisanego na ich urządzeniu.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "polityka pojedynczej organizacji", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": ",", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "polityka wymaganego SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "i", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "polityka administracji odzyskiwaniem", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "z automatycznym zapisem włączy się, gdy ta opcja jest używana.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Uprawnienia w Twojej organizacji zostały zaktualizowane, musisz teraz ustawić hasło główne.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index b911d5b273a..842e6274ffd 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Membros de risco" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total de membros" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Este usuário está usando o login em duas etapas para proteger a sua conta." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Criando conta em" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Dispositivos confiáveis" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Uma vez autenticados, os membros descriptografarão os dados do cofre usando uma chave armazenada no seu dispositivo", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "organização única", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "política,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "Necessário SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "política e", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "gerenciar recuperação de conta", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "política com inscrição automática será ativada quando esta opção for utilizada.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha mestra.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 637c65f644d..6a649f38ad2 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Membros em risco" }, + "atRiskMembersWithCount": { + "message": "Membros em risco ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Aplicações em risco ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Estes membros estão a iniciar sessão em aplicações com palavras-passe fracas, expostas ou reutilizadas." + }, + "atRiskApplicationsDescription": { + "message": "Estas aplicações têm palavras-passe fracas, expostas ou reutilizadas." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Estes membros estão a iniciar sessão no $APPNAME$ com palavras-passe fracas, expostas ou reutilizadas.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total de membros" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Ainda tem 1 convite." }, + "inviteZeroEmailDesc": { + "message": "Restam-lhe 0 convites." + }, "userUsingTwoStep": { "message": "Este utilizador está a utilizar a verificação de dois passos para proteger a sua conta." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Confiável" }, + "needsApproval": { + "message": "Precisa de aprovação" + }, + "areYouTryingtoLogin": { + "message": "Está a tentar iniciar sessão?" + }, + "logInAttemptBy": { + "message": "Tentativa de início de sessão por $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Tipo de dispositivo" + }, + "ipAddress": { + "message": "Endereço IP" + }, + "confirmLogIn": { + "message": "Confirmar início de sessão" + }, + "denyLogIn": { + "message": "Recusar início de sessão" + }, + "thisRequestIsNoLongerValid": { + "message": "Este pedido já não é válido." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Início de sessão confirmado para $EMAIL$ no $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Recusou uma tentativa de início de sessão de outro dispositivo. Se foi realmente o caso, tente iniciar sessão com o dispositivo novamente." + }, + "loginRequestHasAlreadyExpired": { + "message": "O pedido de início de sessão já expirou." + }, + "justNow": { + "message": "Agora mesmo" + }, + "requestedXMinutesAgo": { + "message": "Pedido há $MINUTES$ minutos", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "A criar conta em" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Dispositivos de confiança" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Uma vez autenticados, os membros desencriptam os dados do cofre utilizando uma chave armazenada no seu dispositivo. A", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Os membros não precisarão de uma palavra-passe mestra quando iniciarem sessão com o SSO. A palavra-passe mestra é substituída por uma chave de encriptação armazenada no dispositivo, tornando esse dispositivo fiável. O primeiro dispositivo em que um membro cria a sua conta e inicia sessão será de confiança. Os novos dispositivos terão de ser aprovados por um dispositivo de confiança existente ou por um administrador. A", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "política de organização", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "única,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "política de SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "a política de SSO", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "obrigatória, e a", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "obrigatório e a", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "gestão da recuperação de contas", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "política de administração de recuperação", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "com inscrição automática será ativada quando esta opção for utilizada.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "de conta serão ativadas quando esta opção for utilizada.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "As permissões da sua organização foram atualizadas, exigindo a definição de uma palavra-passe mestra.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "O nome da organização não pode exceder 50 caracteres." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "A sua subscrição será renovada em breve. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "A fatura da sua subscrição foi emitida a $ISSUED_DATE$. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $DUE_DATE$.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "A fatura da sua subscrição não foi paga. Para garantir um serviço ininterrupto, contacte a $RESELLER$ para confirmar a sua renovação antes de $GRACE_PERIOD_END$.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 42d13688444..f7bfbb0e592 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Acest utilizator folosește conectarea în două etape pentru a-și proteja contul." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 28386190d17..2e74ee4094c 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Участники группы риска" }, + "atRiskMembersWithCount": { + "message": "Пользователи повышенного риска ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Приложения с повышенным риском ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Эти пользователи входят в приложения со слабыми, скомпрометированными или повторно используемыми паролями." + }, + "atRiskApplicationsDescription": { + "message": "Эти приложения имеют слабые, скомпрометированные или повторно используемые пароли." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Эти пользователи входят в $APPNAME$ со слабыми, скомпрометированными или повторно используемыми паролями.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Всего участников" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "У вас осталось 1 приглашение." }, + "inviteZeroEmailDesc": { + "message": "У вас осталось 0 приглашений." + }, "userUsingTwoStep": { "message": "Этот пользователь использует двухэтапную аутентификацию для защиты своего аккаунта." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Доверенный" }, + "needsApproval": { + "message": "Требуется одобрение" + }, + "areYouTryingtoLogin": { + "message": "Вы пытаетесь войти?" + }, + "logInAttemptBy": { + "message": "Попытка авторизации через $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Тип устройства" + }, + "ipAddress": { + "message": "IP-адрес" + }, + "confirmLogIn": { + "message": "Подтвердить вход" + }, + "denyLogIn": { + "message": "Запретить вход" + }, + "thisRequestIsNoLongerValid": { + "message": "Этот запрос больше не действителен." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Вход подтвержден для $EMAIL$ на $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Вы отклонили попытку авторизации с другого устройства. Если это действительно были вы, попробуйте авторизоваться с этого устройства еще раз." + }, + "loginRequestHasAlreadyExpired": { + "message": "Запрос на вход истек." + }, + "justNow": { + "message": "Только что" + }, + "requestedXMinutesAgo": { + "message": "Запрошено $MINUTES$ мин назад", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Создание аккаунта" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Доверенные устройства" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "После аутентификации данные хранилища пользователей станут расшифровываться с помощью ключа, хранящегося на их устройстве. При", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "использовании данной опции будут включены политика единой организации,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "политика", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "и политика", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "администрирования восстановления аккаунтов", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "с автоматической регистрацией.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Права доступа организации были обновлены, требуется установить мастер-пароль.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Название организации не может превышать 50 символов." }, - "resellerRenewalWarning": { - "message": "Ваша подписка скоро будет продлена. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Ваша подписка скоро будет продлена. Чтобы обеспечить непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "Счет за вашу подписку был выставлен $ISSUED_DATE$. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$, чтобы подтвердить продление подписки до $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "Счет за вашу подписку был выставлен $ISSUED_DATE$. Чтобы обеспечить непрерывность сервиса, свяжитесь с $RESELLER$, чтобы подтвердить продление подписки до $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "Счет за вашу подписку не был оплачен. Чтобы гарантировать непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "Счет за вашу подписку не был оплачен. Чтобы обеспечить непрерывность сервиса, свяжитесь с $RESELLER$ для подтверждения продления до $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index df5803c0fd9..1896f6938e9 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index d8adb9116ec..2fdcc5b305a 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Ohrozených členov" }, + "atRiskMembersWithCount": { + "message": "Ohrození členovia ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Ohrozené aplikácie ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "Títo členovia sa prihlasujú do aplikácií so slabým, uniknutým alebo viacnásobne použitým heslom." + }, + "atRiskApplicationsDescription": { + "message": "Tieto aplikácie majú slabé, uniknuté alebo opätovne použité heslá." + }, + "atRiskMembersDescriptionWithApp": { + "message": "Títo členovia sa prihlasujú do $APPNAME$ so slabým, uniknutým alebo viacnásobne použitým heslom.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Celkový počet členov" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "Ostáva vám 1 pozvánka." }, + "inviteZeroEmailDesc": { + "message": "Ostáva vám 0 pozvánok." + }, "userUsingTwoStep": { "message": "Tento používateľ používa dvojstupňové overovanie aby si zabezpečil konto." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Dôveryhodné" }, + "needsApproval": { + "message": "Potrebuje súhlas" + }, + "areYouTryingtoLogin": { + "message": "Snažíte sa prihlásiť?" + }, + "logInAttemptBy": { + "message": "Pokus o prihlásenie pomocou $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Typ zariadenia" + }, + "ipAddress": { + "message": "IP adresa" + }, + "confirmLogIn": { + "message": "Potvrdiť prihlásenie" + }, + "denyLogIn": { + "message": "Odmietnuť prihlásenie" + }, + "thisRequestIsNoLongerValid": { + "message": "Táto žiadosť už nie je platná." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Potvrdené prihlásenie pomocou $EMAIL$ na $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Odmietli ste pokus o prihlásenie z iného zariadenia. Ak ste to boli naozaj vy, skúste sa prihlásiť pomocou zariadenia znova." + }, + "loginRequestHasAlreadyExpired": { + "message": "Platnosť žiadosti o prihlásenie už vypršala." + }, + "justNow": { + "message": "Práve teraz" + }, + "requestedXMinutesAgo": { + "message": "Vyžiadané pred $MINUTES$ minútami", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Dôveryhodné zariadenia" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Po autentifikácii, členovia budú dešifrovať dáta v trezore za pomoci kľúča uloženého na ich zariadení.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "jedna organizácia", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "správa obnovy konta", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Povolenia vašej organizácie boli aktualizované, čo si vyžaduje nastavenie hlavného hesla.", @@ -10079,7 +10176,7 @@ "organizationNameMaxLength": { "message": "Meno organizácie nemôže mať viac ako 50 znakov." }, - "resellerRenewalWarning": { + "resellerRenewalWarningMsg": { "message": "Vaše predplatné sa čoskoro obnoví. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $RENEWAL_DATE$.", "placeholders": { "reseller": { @@ -10092,7 +10189,7 @@ } } }, - "resellerOpenInvoiceWarning": { + "resellerOpenInvoiceWarningMgs": { "message": "Faktúra za vaše predplatné bola vystavená dňa $ISSUED_DATE$. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie predplatného pred $DUE_DATE$.", "placeholders": { "reseller": { @@ -10109,7 +10206,7 @@ } } }, - "resellerPastDueWarning": { + "resellerPastDueWarningMsg": { "message": "Faktúra za vaše predplatné nebola uhradená. Aby ste si zabezpečili nepretržitú prevádzku, kontaktujte $RESELLER$ a potvrďte obnovenie pred $GRACE_PERIOD_END$.", "placeholders": { "reseller": { diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 75ad87494f0..92981a60058 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 0f3853331c2..4429fda9683 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Чланови под ризиком" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Укупно чланова" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Овај корисник користи пријаву у два корака за заштиту свог налога." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Креирај налог на" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Поуздани уређаји" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Када се аутентификују, чланови ће дешифровати податке из сефљ користећи кључ сачуван на њиховом уређају", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "јединствена организација", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "полиса,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO потребан", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "полиса, и", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "администрација опоравка налога", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "полисе са аутоматским уписом ће се укључити када се користи ова опција.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Дозволе за вашу организацију су ажуриране, што захтева да поставите главну лозинку.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 06572d08fea..aa9a003027b 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index f9737643631..ec4b495d305 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Denna användare använder tvåstegsverifiering för att skydda sitt konto." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Skapa konto på" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Betrodda enheter" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index e6cce3405d5..19f8bb82e54 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 5f5818581f6..16769d1110b 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 97c0d00dec2..0e775f85d93 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Riskli üyeler" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "Riskli uygulamalar ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Toplam üye" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "1 davetiyeniz kaldı." }, + "inviteZeroEmailDesc": { + "message": "0 davetiyeniz kaldı." + }, "userUsingTwoStep": { "message": "Bu kullanıcı hesabını korumak için iki aşamalı giriş kullanıyor." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Onay gerekiyor" + }, + "areYouTryingtoLogin": { + "message": "Giriş yapmaya mı çalışıyorsunuz?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ giriş denemesi", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Cihaz türü" + }, + "ipAddress": { + "message": "IP adresi" + }, + "confirmLogIn": { + "message": "Girişi onayla" + }, + "denyLogIn": { + "message": "Girişi reddet" + }, + "thisRequestIsNoLongerValid": { + "message": "Bu istek artık geçerli değil." + }, + "logInConfirmedForEmailOnDevice": { + "message": "$DEVICE$ cihazında $EMAIL$ girişi onaylandı", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "Başka bir cihazdan giriş isteğini reddettiniz. Yanlışlıkla yaptıysanız aynı cihazdan yeniden giriş yapmayı deneyin." + }, + "loginRequestHasAlreadyExpired": { + "message": "Giriş isteğinin süresi doldu." + }, + "justNow": { + "message": "Az önce" + }, + "requestedXMinutesAgo": { + "message": "$MINUTES$ dakika önce istendi", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Hesap oluşturuluyor:" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Güvenilen cihazlar" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Üyeler kimliklerini doğruladıktan sonra, cihazlarında saklanan anahtarı kullanarak kasa verilerinin şifresini çözebilecektir. Bu seçeneği kullanırsanız", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "tek kuruluş", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "ilkesi,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO zorunlu", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "ilkesi ve", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "hesap kurtarma yönetimi", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "ilkesi otomatik kayıtla birlikte açılacaktır.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Kuruluş izinleriniz güncellendi ve bir ana parola belirlemeniz gerekiyor.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index d8cd9270763..a3d3904cbd9 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "Ризиковані учасники" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Всього учасників" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "Цей користувач використовує двоетапну перевірку для захисту свого облікового запису." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Створення облікового запису" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Довірені пристрої" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Після автентифікації учасники розшифровуватимуть дані сховища з використанням ключа, збереженого на їхньому пристрої.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "Політику єдиної", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "організації,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "політику обов'язкового", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "SSO та", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "політику адміністрування облікового", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "запису з автоматичним розгортанням буде увімкнено, якщо використовується ця опція.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Оновлено дозволи вашої організації – вимагається встановлення головного пароля.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Назва організації не може перевищувати 50 символів." }, - "resellerRenewalWarning": { - "message": "Ваша передплата невдовзі поновиться. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "Рахунок за вашу передплату випущено $ISSUED_DATE$. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "Рахунок за вашу передплату ще не сплачено. Щоб забезпечити безперебійну роботу, зверніться до $RESELLER$ для підтвердження поновлення до $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index ff9f789f6f4..5a4e81ad93e 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "Trusted devices" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { + "memberDecryptionOptionTdeDescLink1": { "message": "single organization", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { + "memberDecryptionOptionTdeDescPart2": { "message": "policy,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { + "memberDecryptionOptionTdeDescLink2": { "message": "SSO required", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { + "memberDecryptionOptionTdeDescPart3": { "message": "policy, and", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { + "memberDecryptionOptionTdeDescLink3": { "message": "account recovery administration", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Your organization permissions were updated, requiring you to set a master password.", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index ae0b3744ec6..1d37fc4c14a 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "有风险的成员" }, + "atRiskMembersWithCount": { + "message": "有风险的成员 ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "有风险的应用程序 ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "这些成员正在使用弱的、暴露的或重复使用的密码登录到应用程序。" + }, + "atRiskApplicationsDescription": { + "message": "这些应用程序具有弱的、暴露的或重复使用的密码。" + }, + "atRiskMembersDescriptionWithApp": { + "message": "这些成员正在使用弱的、暴露的或重复使用的密码登录到 $APPNAME$。", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "总的成员" }, @@ -816,7 +849,7 @@ "message": "筛选" }, "moveSelectedToOrg": { - "message": "移动所选项目到组织" + "message": "移动所选到组织" }, "deleteSelected": { "message": "删除所选" @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "您还剩下 1 个邀请。" }, + "inviteZeroEmailDesc": { + "message": "您还剩下 0 个邀请。" + }, "userUsingTwoStep": { "message": "此用户正在使用两步登录来保护他们的账户。" }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "信任" }, + "needsApproval": { + "message": "需要批准" + }, + "areYouTryingtoLogin": { + "message": "您正在尝试登录吗?" + }, + "logInAttemptBy": { + "message": "$EMAIL$ 的登录尝试", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "设备类型" + }, + "ipAddress": { + "message": "IP 地址" + }, + "confirmLogIn": { + "message": "确认登录" + }, + "denyLogIn": { + "message": "拒绝登录" + }, + "thisRequestIsNoLongerValid": { + "message": "此请求已失效。" + }, + "logInConfirmedForEmailOnDevice": { + "message": "已确认 $EMAIL$ 在 $DEVICE$ 上的登录", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "您拒绝了一个来自其他设备的登录尝试。若确实是您本人,请尝试再次发起设备登录。" + }, + "loginRequestHasAlreadyExpired": { + "message": "登录请求已过期。" + }, + "justNow": { + "message": "刚刚" + }, + "requestedXMinutesAgo": { + "message": "$MINUTES$ 分钟前已发出请求", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "创建账户至" }, @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "受信任设备" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "验证后,成员将使用存储在他们设备上的密钥解密密码库数据。使用此选项后,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "单一组织", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "策略,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "要求 SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "策略以及具有自动注册的", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "账户恢复管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "策略将被开启。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "您的组织权限已更新,要求您设置主密码。", @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "组织名称不能超过 50 个字符。" }, - "resellerRenewalWarning": { - "message": "您的订阅即将续订。为确保服务不中断,请在 $RENEWAL_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "resellerRenewalWarningMsg": { + "message": "您的订阅即将续期。为确保服务不会中断,请在 $RENEWAL_DATE$ 之前联系 $RESELLER$ 确认您的续订。", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "您的订阅账单已于 $ISSUED_DATE$ 开具。为确保服务不中断,请在 $DUE_DATE$ 之前联系 $RESELLER$ 确认您的续订。", + "resellerOpenInvoiceWarningMgs": { + "message": "您的订阅账单已于 $ISSUED_DATE$ 开具。为确保服务不会中断,请在 $DUE_DATE$ 之前联系 $RESELLER$ 确认您的续订。", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "您的订阅账单尚未支付。为确保服务不中断,请在 $GRACE_PERIOD_END$ 之前联系 $RESELLER$ 确认您的续订。", + "resellerPastDueWarningMsg": { + "message": "您的订阅账单尚未支付。为确保服务不会中断,请在 $GRACE_PERIOD_END$ 之前联系 $RESELLER$ 确认您的续订。", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 002d0d55e0f..4f47a7865eb 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -113,6 +113,39 @@ "atRiskMembers": { "message": "具有風險的成員" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -267,7 +300,7 @@ "message": "安全碼 (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "安全碼 / CVV" }, "identityName": { "message": "身分名稱" @@ -572,7 +605,7 @@ "message": "安全筆記" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "typeLoginPlural": { "message": "登入資料" @@ -605,7 +638,7 @@ "message": "全名" }, "address": { - "message": "Address" + "message": "地址" }, "address1": { "message": "地址 1" @@ -722,7 +755,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "copyValue": { "message": "複製值", @@ -737,7 +770,7 @@ "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "已複製密碼" }, "copyUsername": { "message": "複製使用者名稱", @@ -765,28 +798,28 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "複製網站" }, "copyNotes": { - "message": "Copy notes" + "message": "複製備註" }, "copyAddress": { - "message": "Copy address" + "message": "複製地址" }, "copyPhone": { - "message": "Copy phone" + "message": "複製電話" }, "copyEmail": { "message": "複製電子郵件地址" }, "copyCompany": { - "message": "Copy company" + "message": "複製公司名稱" }, "copySSN": { - "message": "Copy Social Security number" + "message": "複製社會安全號碼" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "複製護照號碼" }, "copyLicenseNumber": { "message": "Copy license number" @@ -1099,13 +1132,13 @@ "message": "建立帳戶" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "第一次使用 Bitwarden?" }, "setAStrongPassword": { "message": "設定一個強密碼" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "設定密碼以完成建立您的帳號。" }, "newAroundHere": { "message": "第一次使用?" @@ -1117,13 +1150,13 @@ "message": "登入" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "登入 Bitwarden" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "此驗證工作階段已逾時。請重試登入。" }, "verifyIdentity": { "message": "核實你的身份" @@ -1174,7 +1207,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", @@ -1193,10 +1226,10 @@ "message": "Account email" }, "requestHint": { - "message": "Request hint" + "message": "請求提示" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "請求密碼提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" @@ -1239,10 +1272,10 @@ "message": "帳戶已建立!現在可以登入了。" }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "您已經登入!" }, "trialAccountCreated": { "message": "帳戶建立成功。" @@ -1260,10 +1293,10 @@ "message": "電子郵件地址" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "您的密碼庫已被鎖定" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "您的帳號已被鎖定。" }, "uuid": { "message": "UUID" @@ -1300,7 +1333,7 @@ "message": "您沒有檢視此集合中所有項目的權限。" }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "您沒有存取此集合的權限" }, "noCollectionsInList": { "message": "沒有可列出的集合。" @@ -1327,7 +1360,7 @@ "message": "通知已傳送至您的裝置。" }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "已傳送通知至您的裝置" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" @@ -1403,7 +1436,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 存取您的帳戶。支援 YubiKey 4 系列、5 系列以及 NEO 裝置。" @@ -1676,10 +1709,10 @@ "message": "沒有可列出的密碼。" }, "clearHistory": { - "message": "Clear history" + "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -1722,7 +1755,7 @@ "message": "請重新登入。" }, "currentSession": { - "message": "Current session" + "message": "目前工作階段" }, "requestPending": { "message": "Request pending" @@ -1798,7 +1831,7 @@ "message": "小心,這些動作無法復原!" }, "dangerZoneDescSingular": { - "message": "Careful, this action is not reversible!" + "message": "小心,此操作無法復原!" }, "deauthorizeSessions": { "message": "取消工作階段授權" @@ -1813,7 +1846,7 @@ "message": "已取消所有工作階段授權" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "此帳號由 $ORGANIZATIONNAME$ 擁有", "placeholders": { "organizationName": { "content": "$1", @@ -2126,19 +2159,19 @@ "message": "輸入您的主密碼以修改兩步驟登入設定。" }, "twoStepAuthenticatorInstructionPrefix": { - "message": "Download an authenticator app such as" + "message": "下載驗證應用程式,例如" }, "twoStepAuthenticatorInstructionInfix1": { - "message": "," + "message": "、" }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "或" }, "twoStepAuthenticatorInstructionSuffix": { - "message": "." + "message": "。" }, "continueToExternalUrlTitle": { - "message": "Continue to $URL$?", + "message": "繼續前往 $URL$ 嗎?", "placeholders": { "url": { "content": "$1", @@ -2147,16 +2180,16 @@ } }, "continueToExternalUrlDesc": { - "message": "You are leaving Bitwarden and launching an external website in a new window." + "message": "您將離開 Bitwarden 並在新視窗開啟外部網站。" }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "繼續前往 bitwarden.com?" }, "twoStepContinueToBitwardenUrlDesc": { "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website." }, "twoStepAuthenticatorScanCodeV2": { - "message": "Scan the QR code below with your authenticator app or enter the key." + "message": "使用驗證應用程式掃描下方的 QR 碼,或輸入金鑰。" }, "twoStepAuthenticatorQRCanvasError": { "message": "Could not load QR code. Try again or use the key below." @@ -3281,6 +3314,9 @@ "inviteSingleEmailDesc": { "message": "You have 1 invite remaining." }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "此使用者正在使用兩步驟登入保護帳戶。" }, @@ -3789,6 +3825,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "登入要求已逾期。" + }, + "justNow": { + "message": "剛剛" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "建立帳號於" }, @@ -3913,7 +4010,7 @@ "message": "未支援您使用的瀏覽器。網頁版密碼庫可能無法正常運作。" }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "您的免費試用將於 $COUNT$ 天後結束。", "placeholders": { "count": { "content": "$1", @@ -3922,7 +4019,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$,您的免費試用將於 $COUNT$ 天後結束。", "placeholders": { "count": { "content": "$2", @@ -3935,7 +4032,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$,您的免費試用明天就結束了。", "placeholders": { "organization": { "content": "$1", @@ -3944,10 +4041,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "您的免費試用明天就結束了。" }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$,您的免費試用今天就結束了。", "placeholders": { "organization": { "content": "$1", @@ -3956,16 +4053,16 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "您的免費試用今天就結束了。" }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "按一下此處來新增付款方式。" }, "joinOrganization": { "message": "加入組織" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "加入 $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -4025,7 +4122,7 @@ "message": "您已要求刪除您的 Bitwarden 帳戶。請點選下方的按鈕確認。" }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "您已要求刪除您的 Bitwarden 組織。" }, "myOrganization": { "message": "我的組織" @@ -4445,7 +4542,7 @@ "message": "已選擇" }, "recommended": { - "message": "Recommended" + "message": "建議" }, "ownership": { "message": "擁有權" @@ -4764,10 +4861,10 @@ "message": "你已成功登入" }, "thisWindowWillCloseIn5Seconds": { - "message": "This window will automatically close in 5 seconds" + "message": "此視窗將在 5 秒後自動關閉。" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "您可以關閉此視窗" }, "includeAllTeamsFeatures": { "message": "包含所有團隊版功能" @@ -4932,7 +5029,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "複製連結" }, "copySendLink": { "message": "複製 Send 連結", @@ -5359,19 +5456,19 @@ "message": "Protect secrets with end-to-end encryption. No more hard coding secrets or sharing through .env files." }, "enhanceDeveloperProductivity": { - "message": "Enhance developer productivity." + "message": "提升開發者生產力。" }, "enhanceDeveloperProductivityDescription": { "message": "Programmatically retrieve and deploy secrets at runtime so developers can focus on what matters most, like improving code quality." }, "strengthenBusinessSecurity": { - "message": "Strengthen business security." + "message": "強化企業安全。" }, "strengthenBusinessSecurityDescription": { "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." }, "tryItNow": { - "message": "Try it now" + "message": "立即體驗" }, "sendRequest": { "message": "Send request" @@ -5383,7 +5480,7 @@ "message": "Bitwarden Secrets Manager" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "更多來自 Bitwarden 的產品" }, "requestAccessToSecretsManager": { "message": "Request access to Secrets Manager" @@ -6570,7 +6667,7 @@ "message": "Generate email" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "值必須介於 $MIN$ 及 $MAX$。", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6630,10 +6727,10 @@ "message": "Username generator" }, "useThisPassword": { - "message": "Use this password" + "message": "使用此密碼" }, "useThisUsername": { - "message": "Use this username" + "message": "使用此使用者名稱" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." @@ -6709,7 +6806,7 @@ "message": "使用外部轉寄服務產生一個電子郵件別名。" }, "forwarderDomainName": { - "message": "Email domain", + "message": "電子信箱網域", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -6995,7 +7092,7 @@ "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." + "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." @@ -7852,13 +7949,13 @@ "message": "或" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "使用生物辨識解鎖" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "使用 PIN 碼解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "licenseAndBillingManagement": { "message": "授權和計費管理" @@ -8171,33 +8268,33 @@ "trustedDevices": { "message": "可信任的裝置" }, - "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "通過驗證以後,成員將使用儲存在裝置的金鑰來解密密碼庫資料。使用此選項後,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart1": { + "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "單一組織", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink1": { + "message": "single organization", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "原則,", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart2": { + "message": "policy,", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "要求 SSO", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink2": { + "message": "SSO required", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "原則以及具有自動注冊的", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart3": { + "message": "policy, and", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "帳戶復原管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescLink3": { + "message": "account recovery administration", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, - "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "原則將被開啓。", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "memberDecryptionOptionTdeDescPart4": { + "message": "policy will turn on when this option is used.", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { "message": "您的組織權限已更新,因此您需要設定主密碼。", @@ -8750,10 +8847,10 @@ } }, "addField": { - "message": "Add field" + "message": "新增欄位" }, "editField": { - "message": "Edit field" + "message": "編輯欄位" }, "items": { "message": "項目" @@ -9117,7 +9214,7 @@ } }, "smIntegrationTooltip": { - "message": "Set up $INTEGRATION$.", + "message": "設定 $INTEGRATION$。", "placeholders": { "integration": { "content": "$1", @@ -9126,7 +9223,7 @@ } }, "smSdkTooltip": { - "message": "View $SDK$ repository", + "message": "檢視 $SDK$ 儲存庫", "placeholders": { "sdk": { "content": "$1", @@ -9144,7 +9241,7 @@ } }, "smSdkAriaLabel": { - "message": "view $SDK$ repository in a new tab.", + "message": "在新分頁中檢視 $SDK$ 儲存庫。", "placeholders": { "sdk": { "content": "$1", @@ -9201,13 +9298,13 @@ "message": "從提供者入口網站管理計費" }, "continueSettingUpFreeTrial": { - "message": "Continue setting up your free trial of Bitwarden" + "message": "繼續設定您的 Bitwarden 免費試用" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "繼續設定您的 Bitwarden 密碼管理器免費試用" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue setting up your free trial of Bitwarden Secrets Manager" + "message": "繼續設定您的 Bitwarden 機密管理免費試用" }, "enterTeamsOrgInfo": { "message": "Enter your Teams organization information" @@ -9461,7 +9558,7 @@ "message": "Client details" }, "downloadCSV": { - "message": "Download CSV" + "message": "下載 CSV" }, "monthlySubscriptionUserSeatsMessage": { "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. " @@ -9492,10 +9589,10 @@ "message": "After making updates in the Bitwarden cloud server, upload your license file to apply the most recent changes." }, "addToFolder": { - "message": "Add to folder" + "message": "新增到資料夾" }, "selectFolder": { - "message": "Select folder" + "message": "選擇資料夾" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -9592,10 +9689,10 @@ "message": "Learn more about member roles and permissions" }, "whatIsACvvNumber": { - "message": "What is a CVV number?" + "message": "CVV 號碼是什麼?" }, "learnMoreAboutApi": { - "message": "Learn more about Bitwarden's API" + "message": "瞭解更多關於 Bitwarden API 的資訊" }, "fileSends": { "message": "File Sends" @@ -9697,10 +9794,10 @@ "message": "Fingerprint" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "公開金鑰" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -9797,7 +9894,7 @@ "message": "Enter the the field's html id, name, aria-label, or placeholder." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "包含大寫字元", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -9805,7 +9902,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -9813,7 +9910,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "包含數字", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -9821,7 +9918,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "包含特殊字元", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -9829,10 +9926,10 @@ "description": "Label for the password generator special characters checkbox" }, "addAttachment": { - "message": "Add attachment" + "message": "新增附件" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "最大檔案大小為 500MB" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" @@ -9854,7 +9951,7 @@ "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "刪除 $NAME$", "placeholders": { "name": { "content": "$1", @@ -9878,7 +9975,7 @@ "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "已刪除 $NAME$", "placeholders": { "name": { "content": "$1", @@ -9929,7 +10026,7 @@ "message": "This action is not applicable to any of the selected members." }, "deletedSuccessfully": { - "message": "Deleted successfully" + "message": "刪除成功" }, "freeFamiliesSponsorship": { "message": "Remove Free Bitwarden Families sponsorship" @@ -9959,7 +10056,7 @@ "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." }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { "message": "Do you have reliable access to your email, $EMAIL$?", @@ -9977,10 +10074,10 @@ "message": "Yes, I can reliably access my email" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" }, "removeMembers": { "message": "Remove members" @@ -10079,8 +10176,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10092,8 +10189,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10109,8 +10206,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", @@ -10129,7 +10226,7 @@ "message": "Restart your subscription" }, "suspendedManagedOrgMessage": { - "message": "Contact $PROVIDER$ for assistance.", + "message": "聯繫 $PROVIDER$ 以取得協助。", "placeholders": { "provider": { "content": "$1", From 5be36c08d6d97d5d9c07cd8e7d19e26ea7699efc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:50:28 +0100 Subject: [PATCH 252/270] [deps] Architecture: Update lint-staged to v15.4.1 (#12954) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- 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 28624e50a9b..64d4cc7d993 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,7 +156,7 @@ "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", - "lint-staged": "15.4.0", + "lint-staged": "15.4.1", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.49", @@ -22017,9 +22017,9 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.0.tgz", - "integrity": "sha512-UdODqEZiQimd7rCzZ2vqFuELRNUda3mdv7M93jhE4SmDiqAj/w/msvwKgagH23jv2iCPw6Q5m+ltX4VlHvp2LQ==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.1.tgz", + "integrity": "sha512-P8yJuVRyLrm5KxCtFx+gjI5Bil+wO7wnTl7C3bXhvtTaAFGirzeB24++D0wGoUwxrUKecNiehemgCob9YL39NA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8b692a57ac9..9f58ed160bd 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", - "lint-staged": "15.4.0", + "lint-staged": "15.4.1", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.49", From 93fc7bda2fbe6d7faf728b6b46eebe11e7e95fbf Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:54:37 +0000 Subject: [PATCH 253/270] Autosync the updated translations (#12991) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/ca/messages.json | 98 +++++++++++------------ apps/desktop/src/locales/ko/messages.json | 62 +++++++------- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 46f876e560a..8b4ee8e276d 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -27,7 +27,7 @@ "message": "Nota segura" }, "typeSshKey": { - "message": "SSH key" + "message": "Clau SSH" }, "folders": { "message": "Carpetes" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Benvingut/da de nou" }, "moveToOrgDesc": { "message": "Trieu una organització a la qual vulgueu desplaçar aquest element. El trasllat a una organització transfereix la propietat de l'element a aquesta organització. Ja no sereu el propietari directe d'aquest element una vegada s'haja mogut." @@ -181,16 +181,16 @@ "message": "Adreça" }, "sshPrivateKey": { - "message": "Private key" + "message": "Clau privada" }, "sshPublicKey": { - "message": "Public key" + "message": "Clau pública" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Empremta digital" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipus de clau" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,22 +205,22 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "S'ha generat una nova clau SSH" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "La contrasenya que heu introduït és incorrecta." }, "importSshKey": { - "message": "Import" + "message": "Importa" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Confirma contrasenya" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Introduïu la contrasenya per a la clau SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Introduïu la contrasenya" }, "sshAgentUnlockRequired": { "message": "Please unlock your vault to approve the SSH key request." @@ -250,7 +250,7 @@ "message": "Error" }, "decryptionError": { - "message": "Decryption error" + "message": "Error de desxifrat" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden could not decrypt the vault item(s) listed below." @@ -337,7 +337,7 @@ "message": "Genera contrasenya" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera frase de pas" }, "type": { "message": "Tipus" @@ -481,7 +481,7 @@ "message": "Copy SSH private key" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Copia frase de pas", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -512,11 +512,11 @@ "message": "Caràcters especials (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "Inclou", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inclou majúscules", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -524,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inclou minúscules", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -532,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Inclou números", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -540,7 +540,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inclou caràcters especials", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -575,7 +575,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Eviteu caràcters ambigus", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -638,7 +638,7 @@ "message": "Crea un compte" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nou a Bitwarden?" }, "setAStrongPassword": { "message": "Estableix una contrasenya segura" @@ -653,13 +653,13 @@ "message": "Log in to Bitwarden" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Inicieu sessió amb la clau de pas" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Inici de sessió amb dispositiu" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Usa inici de sessió únic" }, "submit": { "message": "Envia" @@ -705,10 +705,10 @@ "message": "Pista de la contrasenya mestra" }, "joinOrganization": { - "message": "Uneix-te a l'organització" + "message": "Uniu-vos a l'organització" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Uniu-vos a $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -723,13 +723,13 @@ "message": "Configuració" }, "accountEmail": { - "message": "Account email" + "message": "Correu electrònic del compte" }, "requestHint": { - "message": "Request hint" + "message": "Sol·licita pista" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Sol·licita pista de la contrasenya" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" @@ -1087,16 +1087,16 @@ "message": "La caixa forta està bloquejada. Comproveu la contrasenya mestra per continuar." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "El compte està bloquejat" }, "or": { - "message": "or" + "message": "o" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloqueja amb dades biomètriques" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Desbloqueja amb contrasenya mestra" }, "unlock": { "message": "Desbloqueja" @@ -1127,7 +1127,7 @@ "message": "Temps d'espera de la caixa forta" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Temps d'espera" }, "vaultTimeoutDesc": { "message": "Trieu quan es tancarà la vostra caixa forta i feu l'acció seleccionada." @@ -1334,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Copia el correu electrònic" }, "copySecurityCode": { "message": "Copia el codi de seguretat", @@ -1407,13 +1407,13 @@ "message": "Historial de les contrasenyes" }, "generatorHistory": { - "message": "Generator history" + "message": "Historial del generador" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Neteja l'historial del generador" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Si continueu, totes les entrades se suprimiran permanentment de l'historial del generador. Esteu segur que voleu continuar?" }, "clear": { "message": "Esborra", @@ -1423,10 +1423,10 @@ "message": "No hi ha cap contrasenya a llistar." }, "clearHistory": { - "message": "Clear history" + "message": "Neteja l'historial" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Res a mostrar" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -1506,7 +1506,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Còpia correcta" }, "errorRefreshingAccessToken": { "message": "Access Token Refresh Error" @@ -2470,7 +2470,7 @@ "message": "Bloquejat" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "La caixa forta està bloquejada" }, "unlocked": { "message": "Desbloquejat" @@ -2746,7 +2746,7 @@ "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Necessiteu una altra opció?" }, "fingerprintMatchInfo": { "message": "Assegureu-vos que la vostra caixa forta estiga desbloquejada i que la frase d'empremta digital coincidisca amb l'altre dispositiu." @@ -2889,13 +2889,13 @@ "message": "Comproveu les filtracions de dades conegudes per a aquesta contrasenya" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Connectat!" }, "important": { "message": "Important:" }, "accessing": { - "message": "Accessing" + "message": "Accedint a" }, "accessTokenUnableToBeDecrypted": { "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." @@ -3374,10 +3374,10 @@ "message": "Dades" }, "fileSends": { - "message": "File Sends" + "message": "Sends de fitxers" }, "textSends": { - "message": "Text Sends" + "message": "Sends de text" }, "ssoError": { "message": "No free ports could be found for the sso login." @@ -3410,7 +3410,7 @@ "message": "Authorize" }, "deny": { - "message": "Deny" + "message": "Denega" }, "sshkeyApprovalTitle": { "message": "Confirm SSH key usage" diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 38b1a2b9a55..092cdf08afc 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "돌아온 것을 환영합니다" }, "moveToOrgDesc": { "message": "이 항목을 이동할 조직을 선택하십시오. 항목이 조직으로 이동되면 소유권이 조직으로 이전됩니다. 일단 이동되면, 더는 이동된 항목의 직접적인 소유자가 아니게 됩니다." @@ -181,16 +181,16 @@ "message": "주소" }, "sshPrivateKey": { - "message": "Private key" + "message": "비공개 키" }, "sshPublicKey": { - "message": "Public key" + "message": "공개 키" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "지문" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "키 유형" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,31 +205,31 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "새로운 SSH 키가 생성되었습니다." }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "입력된 비밀번호가 올바르지 않습니다." }, "importSshKey": { - "message": "Import" + "message": "가져오기" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "비밀번호 확인" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "SSH 키의 비밀번호 입력" }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "비밀번호 입력" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "SSH 키 요청을 승인하려면 보관함을 해제하세요." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "SSH 키 요청시간 만료" }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "SSH 에이전트 활성화" }, "enableSshAgentDesc": { "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." @@ -475,10 +475,10 @@ "message": "비밀번호 복사" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "SSH 키 재생성" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "SSH 비공개 키 복사" }, "copyPassphrase": { "message": "Copy passphrase", @@ -512,11 +512,11 @@ "message": "특수 문자 (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "포함", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "대문자 포함", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -524,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "소문자 포함", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -532,7 +532,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "숫자 포함", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -540,7 +540,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "특수 문자 포함", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -579,7 +579,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "기업 정책에 따른 요구사항이 생성기 옵션에 적용되어 있습니다.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -617,7 +617,7 @@ "message": "최대 파일 크기는 500MB입니다." }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "암호화 키 마이그레이션이 필요합니다. 웹 보관함에 로그인하여 암호화 키를 업데이트하세요." }, "editedFolder": { "message": "폴더 편집함" @@ -638,28 +638,28 @@ "message": "계정 만들기" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Bitwarden이 처음 이신가요?" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "강력한 비밀번호 설정" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "비밀번호를 설정하여 계정 생성을 완료하세요" }, "logIn": { "message": "로그인" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Bitwarden 로그인" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "패스키로 로그인" }, "loginWithDevice": { - "message": "Log in with device" + "message": "기기로 로그인" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Single sign-on(SSO) 사용" }, "submit": { "message": "보내기" @@ -693,7 +693,7 @@ } }, "masterPassword": { - "message": "Master password" + "message": "마스터 비밀번호" }, "masterPassImportant": { "message": "Your master password cannot be recovered if you forget it!" From 8498de391f7e5c9212684d085800ec3d4c2a2197 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:55:05 +0100 Subject: [PATCH 254/270] Autosync the updated translations (#12992) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 32 ---------- apps/browser/src/_locales/az/messages.json | 32 ---------- apps/browser/src/_locales/be/messages.json | 32 ---------- apps/browser/src/_locales/bg/messages.json | 32 ---------- apps/browser/src/_locales/bn/messages.json | 32 ---------- apps/browser/src/_locales/bs/messages.json | 32 ---------- apps/browser/src/_locales/ca/messages.json | 62 +++++-------------- apps/browser/src/_locales/cs/messages.json | 32 ---------- apps/browser/src/_locales/cy/messages.json | 32 ---------- apps/browser/src/_locales/da/messages.json | 32 ---------- apps/browser/src/_locales/de/messages.json | 32 ---------- apps/browser/src/_locales/el/messages.json | 32 ---------- apps/browser/src/_locales/en_GB/messages.json | 32 ---------- apps/browser/src/_locales/en_IN/messages.json | 32 ---------- apps/browser/src/_locales/es/messages.json | 32 ---------- apps/browser/src/_locales/et/messages.json | 32 ---------- apps/browser/src/_locales/eu/messages.json | 32 ---------- apps/browser/src/_locales/fa/messages.json | 32 ---------- apps/browser/src/_locales/fi/messages.json | 32 ---------- apps/browser/src/_locales/fil/messages.json | 32 ---------- apps/browser/src/_locales/fr/messages.json | 32 ---------- apps/browser/src/_locales/gl/messages.json | 32 ---------- apps/browser/src/_locales/he/messages.json | 32 ---------- apps/browser/src/_locales/hi/messages.json | 32 ---------- apps/browser/src/_locales/hr/messages.json | 32 ---------- apps/browser/src/_locales/hu/messages.json | 32 ---------- apps/browser/src/_locales/id/messages.json | 32 ---------- apps/browser/src/_locales/it/messages.json | 32 ---------- apps/browser/src/_locales/ja/messages.json | 32 ---------- apps/browser/src/_locales/ka/messages.json | 32 ---------- apps/browser/src/_locales/km/messages.json | 32 ---------- apps/browser/src/_locales/kn/messages.json | 32 ---------- apps/browser/src/_locales/ko/messages.json | 44 ++----------- apps/browser/src/_locales/lt/messages.json | 32 ---------- apps/browser/src/_locales/lv/messages.json | 32 ---------- apps/browser/src/_locales/ml/messages.json | 32 ---------- apps/browser/src/_locales/mr/messages.json | 32 ---------- apps/browser/src/_locales/my/messages.json | 32 ---------- apps/browser/src/_locales/nb/messages.json | 32 ---------- apps/browser/src/_locales/ne/messages.json | 32 ---------- apps/browser/src/_locales/nl/messages.json | 38 +----------- apps/browser/src/_locales/nn/messages.json | 32 ---------- apps/browser/src/_locales/or/messages.json | 32 ---------- apps/browser/src/_locales/pl/messages.json | 32 ---------- apps/browser/src/_locales/pt_BR/messages.json | 32 ---------- apps/browser/src/_locales/pt_PT/messages.json | 32 ---------- apps/browser/src/_locales/ro/messages.json | 32 ---------- apps/browser/src/_locales/ru/messages.json | 32 ---------- apps/browser/src/_locales/si/messages.json | 32 ---------- apps/browser/src/_locales/sk/messages.json | 32 ---------- apps/browser/src/_locales/sl/messages.json | 32 ---------- apps/browser/src/_locales/sr/messages.json | 32 ---------- apps/browser/src/_locales/sv/messages.json | 32 ---------- apps/browser/src/_locales/te/messages.json | 32 ---------- apps/browser/src/_locales/th/messages.json | 32 ---------- apps/browser/src/_locales/tr/messages.json | 36 +---------- apps/browser/src/_locales/uk/messages.json | 32 ---------- apps/browser/src/_locales/vi/messages.json | 32 ---------- apps/browser/src/_locales/zh_CN/messages.json | 32 ---------- apps/browser/src/_locales/zh_TW/messages.json | 40 ++---------- apps/browser/store/locales/ru/copy.resx | 2 +- 61 files changed, 31 insertions(+), 1951 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 30f20dfff1d..625454de8c6 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "استيراد بياناتك إلى Bitwarden؟", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "حماية بياناتك الأخيرة واستيرادك إلى Bitwarden؟", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "حفظ كملف غير مشفر", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "استيراد إلى Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "جارِ الاستيراد...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "لقد تم استيراد البيانات بنجاح!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "خطأ في الاستيراد. تحقق من وحدة التحكم للحصول على التفاصيل.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "تم مواجهة خطأ في الشبكة أثناء الاستيراد.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "الاسم البديل للنطاق" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 9184b84d735..8e7d2201f28 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -3425,38 +3425,6 @@ "message": "Yığcamlaşdırmanı aç/bağla", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Datanız Bitwarden-ə köçürülsün?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass datanızı qoruyub Bitwarden-ə köçürürsünüz?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Şifrələnməmiş fayl olaraq saxla", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden-ə köçür", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Daxilə köçürülür...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data uğurla daxilə köçürüldü!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Daxilə köçürmə xətası. Detallar üçün konsolu yoxlayın.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Daxilə köçürmə zamanı şəbəkə xətası baş verdi.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domen ləqəbi" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 66545d5666b..57496e83f41 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -3425,38 +3425,6 @@ "message": "Згарнуць/Разгарнуць", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Імпартаванне...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Даныя паспяхова імпартаваны!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Мянушка дамена" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index d4f81883672..6e9f992f5ba 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -3425,38 +3425,6 @@ "message": "Превключване на свиването", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Внасяне на данните Ви в Битуорден?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Защитаване на данните Ви от LastPass и внасяне в Битуорден?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Запазване като нешифрован файл", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Внасяне в Битуорден", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Внасяне…", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Данните бяха внесени успешно!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Грешка при внасянето. Вижте конзолата за подробности.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "По време на внасянето възникна мрежова грешка.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Псевдонимен домейн" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 8180813a2cf..219071238e9 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 0bd4144f6cd..81c162e91be 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index ec7682f5013..89ab22c1794 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -20,16 +20,16 @@ "message": "Crea un compte" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nou a Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Inicieu sessió amb la clau de pas" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Inici de sessió únic" }, "welcomeBack": { - "message": "Welcome back" + "message": "Benvingut/da de nou" }, "setAStrongPassword": { "message": "Estableix una contrasenya segura" @@ -120,7 +120,7 @@ "message": "Copia contrasenya" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Copia frase de pas" }, "copyNote": { "message": "Copia nota" @@ -177,7 +177,7 @@ "message": "Copia notes" }, "fill": { - "message": "Fill", + "message": "Emplena", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +193,10 @@ "message": "Emplena automàticament l'identitat" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Emplena el codi de verificació" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Emplena el codi de verificació", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -443,7 +443,7 @@ "message": "Genera contrasenya" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Genera frase de pas" }, "regeneratePassword": { "message": "Regenera contrasenya" @@ -576,7 +576,7 @@ "message": "Notes" }, "privateNote": { - "message": "Private note" + "message": "Nota privada" }, "note": { "message": "Nota" @@ -682,7 +682,7 @@ "message": "Temps d'espera de la caixa forta" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Temps d'espera" }, "lockNow": { "message": "Bloqueja ara" @@ -2391,7 +2391,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Detalls de l'enviament", + "message": "Detalls del Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { @@ -2408,7 +2408,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Ocultar el text per defecte" + "message": "Amaga el text per defecte" }, "expired": { "message": "Caducat" @@ -2466,7 +2466,7 @@ "message": "Data de supressió" }, "deletionDateDescV2": { - "message": "L'enviament s'esborrarà permanentment en aquesta data.", + "message": "El Send se suprimirà permanentment en aquesta data.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -2849,7 +2849,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " Utilitzeu paraules $RECOMMENDED$ o més per generar una frase de pas segura.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3425,38 +3425,6 @@ "message": "Redueix/Amplia", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Voleu importar les vostres dades a Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protegiu les vostres dades de LastPass i importeu-les a Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Guarda com a fitxer sense xifrar", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importa a Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "S'està important...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Les dades s'han importat correctament!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "S'ha produït un error en importar. Consulteu la consola per obtenir més informació.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "S'ha trobat un error de xarxa durant la importació.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alies de domini" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index c40ddfc6ed9..0a05974279a 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -3425,38 +3425,6 @@ "message": "Přepnout sbalení", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importovat data do Bitwardenu?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Chránit Vaše data LastPass a importovat do Bitwardenu?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Uložit jako nešifrovaný soubor", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importovat do Bitwardenu", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importování...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data byla úspěšně importována!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Chyba při importu. Podrobnosti naleznete v konzoli.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Při importu došlo k chybě sítě.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Doména aliasu" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index cfa74caaa32..cbaa31fea30 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Hoffech chi fewnforio'ch data i Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Yn mewnforio...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Cafodd y data ei fewnforio'n llwyddiannus!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index c5e69fc375b..628ce983c09 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -3425,38 +3425,6 @@ "message": "Fold sammen/ud", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importér data til Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Beskyt LastPass-data og importér dem til Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Gem som ukrypteret fil", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importér til Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importerer...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data er nu importeret!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fejl under import. Tjek konsollen for detaljer.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Netværksfejl opstod under import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliasdomæne" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 872307dd945..86d9d513e40 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -3425,38 +3425,6 @@ "message": "Ein-/ausklappen", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Deine Daten in Bitwarden importieren?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Deine LastPass-Daten schützen und in Bitwarden importieren?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Als unverschlüsselte Datei speichern", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "In Bitwarden importieren", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Wird importiert...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Daten erfolgreich importiert!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fehler beim Importieren. Überprüfe die Konsole für Details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Netzwerkfehler beim Importieren.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias-Domain" }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 37ddbc081f7..16fd5094fdd 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -3425,38 +3425,6 @@ "message": "Εναλλαγή σύμπτυξης", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Εισαγωγή των δεδομένων σας στο Bitwarden;", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Προστατέψτε τα δεδομένα LastPass και εισαγάγετε στο Bitwarden;", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Αποθήκευση ως μη κρυπτογραφημένο αρχείο", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Εισαγωγή στο Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Εισαγωγή...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Επιτυχής εισαγωγή δεδομένων!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Σφάλμα εισαγωγής. Ελέγξτε την κονσόλα για λεπτομέρειες.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Εμφανίστηκε σφάλμα δικτύου κατά την εισαγωγή.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Ψευδώνυμο τομέα" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 5a3ca3893b7..4ea532f1e35 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 0783f0e9172..38724849736 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 37a823b77f4..2f87902cedb 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -3425,38 +3425,6 @@ "message": "Colapsar/Expandir", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "¿Quiere importar sus datos a Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "¿Quiere proteger sus datos de LastPass e importarlos a Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Guardar como archivo no cifrado", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar a Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importando...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Se importaron los datos correctamente.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Se produjo un error al importar. Revise la consola para obtener detalles.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Se produjo un error de red durante la importación.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Seudónimo del dominio" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index b4401bd8850..5ee8566dc33 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -3425,38 +3425,6 @@ "message": "Peida", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Impordin andmed Bitwardenisse?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Kaitse oma LastPassi andmeid ja impordi need Bitwardenisse?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Salvesta ilma krüpteeringuta failina", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Impordi Bitwardenisse", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importimine...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Andmed on edukalt imporditud!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Ilmnes viga. Vaata täpsemaid andmeid konsoolist.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Importimisel ilmnes võrgu viga.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domeen" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 67d45a17458..d7bcfc2838e 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Inportatzen...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Datuak zuzen inportatu dira!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Errorea gertatu da inportatzean. Begiratu xehetasunak kontsolan.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Sareko errorea gertatu da inportatzerakoan.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 1eff69e292f..3cb1581b98f 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -3425,38 +3425,6 @@ "message": "دکمه بستن", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "دامنه مستعار" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 143cc603144..9e2f18ea72e 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -3425,38 +3425,6 @@ "message": "Laajenna tai supista", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Haluatko tuoda tietosi Bitwardeniin?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Haluatko suojata LastPass-tietosi tuomalla ne Bitwardeniin?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Tallenna salaamattomana tiedostona", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Tuo Bitwardeniin", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Tuodaan...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Tietojen tuonti onnistui.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Tuontivirhe. Näet isätietoja hallinnasta.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Verkkovirhe tuonnin aikana.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliaksen verkkotunnus" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 0c24db214a2..62e962e309a 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 890d00b644f..b1ebfe05fe1 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -3425,38 +3425,6 @@ "message": "Déplier/Replier", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importer vos données dans Bitwarden ?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protéger vos données LastPass et importer dans Bitwarden ?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Enregistrer en tant que fichier non chiffré", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importer vers Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importation en cours...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Données importées avec succès !", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erreur lors de l'importation. Consultez la console pour plus de détails.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Une erreur réseau s'est produite lors de l'importation.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domaine de l'alias" }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 9f5e7771a14..3d352359e69 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -3425,38 +3425,6 @@ "message": "Colapsar/Expandir", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importar os teus datos a Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protexer os teus datos de LastPass e importar a Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Gardar como arquivo sen cifrar", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar a Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importando...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Datos importados con éxito!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erro importando. Comproba a consola para máis detalle.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Aconteceu un erro de rede durante a importación.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias do dominio" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 19c0d292d14..b66d0a4aa24 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 3db3ef3e293..8927c78e16b 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "डोमेन उपनाम" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 8d830fd6dc5..20583c4cbbd 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -3425,38 +3425,6 @@ "message": "Sažmi/Proširi", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Uvezi svoje podatke u Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Zaštititi svoje LastPass podatke i uvezi ih u Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Spremi kao nekriptiranu datoteku", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Uvezi u Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Uvoz...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Uvoz podataka u trezor uspješan!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Greška pri uvozu. Provjeri konzolu za detalje.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Došlo je do mrežne greške tijekom uvoza.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domene" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 07bc92c342a..bcde4db3c4f 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -3425,38 +3425,6 @@ "message": "Összezárás váltás", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Adatok importálása a Bitwardenbe?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "A LastPass adatok megvédése és importálása a Bitwardenbe?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Mentés titkosítatlan fájlként", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importálás a Bitwardenbe", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importálás...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Az adatok sikeresen importálásra kerültek.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Hiba történt az importálás során. A részletekért ellenőrizzük a konzolt.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Hálózati hiba történt az importálás során.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Áldomain" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 9755e7322a2..fedfb0cec1f 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -3425,38 +3425,6 @@ "message": "Saklar lipat", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Impor data Anda ke Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Lindungi data LastPass Anda dan impor ke Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Simpan sebagai berkas yang tidak dienkripsi", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Impor ke Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Mengimpor...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data berhasil diimpor!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Gagal mengimpor. Periksa konsol untuk rinciannya.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Kesalahan jaringan ditemui ketika mengimpor.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domain alias" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index e0a868717a5..53b94a951c1 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -3425,38 +3425,6 @@ "message": "Comprimi/espandi", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importare i tuoi dati su Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Proteggere i tuoi dati LastPass e importarli su Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Salva come file non crittografato", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importa su Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importazione in corso...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dati importati!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Errore durante l'importazione. Controlla la console per i dettagli.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Errore di connessione durante l'importazione.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Dominio alias" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 81b81cf16c8..1824c9314ee 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -3425,38 +3425,6 @@ "message": "開く/閉じる", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Bitwarden にデータをインポートしますか?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass データを Bitwarden にインポートしますか?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "暗号化されていないファイルとして保存", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden にインポート", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "インポート中...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "データをインポートしました!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "インポート中にエラーが発生しました。詳細はコンソールを確認してください。", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "インポート中にネットワークエラーが発生しました。", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "エイリアスドメイン" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 9bd12ce6017..1f8f2766e7b 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "შემოტანა...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index ad7d2582146..a7913bbfc9b 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index d613ba26953..ce85c7ea820 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 8aad2f586c7..c264492e0d2 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -987,7 +987,7 @@ "message": "보관함에 항목이 없을 경우 추가하라는 메시지를 표시합니다. 모든 로그인된 계정에 적용됩니다." }, "showCardsInVaultView": { - "message": "보관함 보기에서 카드 자동완성 제안를 표시" + "message": "보관함 보기에서 카드 자동 완성 제안을 표시" }, "showCardsCurrentTab": { "message": "탭 페이지에 카드 표시" @@ -996,7 +996,7 @@ "message": "간편한 자동완성을 위해 탭에 카드 항목들을 나열" }, "showIdentitiesInVaultView": { - "message": "보관함 보기에서 신원들의 자동완성 제안을 표시" + "message": "보관함 보기에서 신원들의 자동 완성 제안을 표시" }, "showIdentitiesCurrentTab": { "message": "탭 페이지에 신원들을 표시" @@ -1005,7 +1005,7 @@ "message": "간편한 자동완성을 위해 탭에 신원 항목들을 나열" }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "보관함 보기에서 항목을 클릭하여 자동 완성" }, "clearClipboard": { "message": "클립보드 비우기", @@ -1107,10 +1107,10 @@ "message": "이 비밀번호는 이 파일을 파일 내보내거나, 가져오는데 사용됩니다." }, "accountRestrictedOptionDescription": { - "message": "계정의 사용자 이름과 마스터 비밀번호에서 파생된 계정 암호화 키를 사용하여 내보내기를 암호화하고, 현재 Bitwarden계정으로 가져오기를 제한해보세요. " + "message": "계정의 사용자 이름과 마스터 비밀번호에서 파생된 계정 암호화 키를 사용하여 내보내기를 암호화하고, 현재 Bitwarden계정으만 가져오기를 제한합니다." }, "passwordProtectedOptionDescription": { - "message": "파일 비밀번호를 설정하여, 내보내기를 암호화하고, 해독에 그 파일 비밀번호를 사용하는 Bitwarden계정에 가져오세요." + "message": "파일 비밀번호를 설정하여 내보내기를 암호화하고, 어느 Bitwarden 계정으로든 해독에 그 파일 비밀번호를 사용하여 가져오세요." }, "exportTypeHeading": { "message": "내보내기 유형" @@ -1478,7 +1478,7 @@ "message": "카드를 제안으로 표시" }, "showInlineMenuOnIconSelectionLabel": { - "message": "아이콘을 선택하면 제안이 표시됩니다." + "message": "아이콘을 선택할 때 제안을 표시" }, "showInlineMenuOnFormFieldsDescAlt": { "message": "로그인한 모든 계정에 적용" @@ -3425,38 +3425,6 @@ "message": "토글이 붕괴됨", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "데이터를 Bitwarden으로 가져오시겠습니까?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass 데이터를 보호하고 Bitwarden으로 가져오시겠습니까?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "암호화되지 않은 파일로 저장", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden으로 가져오기", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "가져오는 중...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "데이터 가져오기 성공!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "가져오는 중 오류가 발생했습니다. 자세한 내용은 콘솔을 확인하세요.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "가져오기 중에 네트워크 오류가 발생했습니다.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "도메인 별칭" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index a29c60d5e8c..f76ae921425 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -3425,38 +3425,6 @@ "message": "Perjungti sutrumpinimą", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importuoti duomenis į Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Apsaugoti LastPass duomenis ir importuoti į Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Išsaugoti kaip neužšifruotą failą", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importuoti į Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importuojama...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Duomenys sėkmingai importuoti.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Klaida importuojant. Išsamesnės informacijos patikrink konsolėje.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Importuojant įvyko tinklo klaida.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domeno slapyvardis" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index c1cdd0182a9..d18fbb92039 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -3425,38 +3425,6 @@ "message": "Pārslēgt sakļaušanu", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Ievietot datus Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Aizsargāt LastPass datus un ievietot tos Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Saglabāt kā nešifrētu datni", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Ievietot Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Ievieto...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dati veiksmīgi ievietoti.", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Kļūda ievietošanā. Jāpārbauda konsole, lai iegūtu vairāk informācijas.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Ievietošanas laikā atgadījās tīkla kļūda.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aizstājdomēns" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index f985f6377ac..cfbb1972388 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 26c0af364ad..05d6034acec 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index ad7d2582146..a7913bbfc9b 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 17cd112b2e8..df676d2a3ba 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Vil du importere dataene dine til Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Lagre som ukryptert fil", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importer til Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importerer …", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dataene ble vellykket importert!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Feil under importering. Sjekk loggkonsollen for detaljer.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias-domene" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index ad7d2582146..a7913bbfc9b 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index c8e0b46f7e4..8a70ea4548c 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "Thuis, op werk of onderweg. Bitwarden beveiligt makkelijk all je wachtwoorden, passkeys en gevoelige informatie", + "message": "Thuis, op werk of onderweg. Bitwarden beveiligt makkelijk al je wachtwoorden, passkeys en gevoelige informatie", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -38,7 +38,7 @@ "message": "Rond het aanmaken van je account af met het instellen van een wachtwoord" }, "enterpriseSingleSignOn": { - "message": "Single sign-on voor bedrijven" + "message": "Enterprise Single Sign-On" }, "cancel": { "message": "Annuleren" @@ -409,7 +409,7 @@ "message": "Ontdek Bitwarden community-forums" }, "contactSupport": { - "message": "Contacteer Bitwarden support" + "message": "Contacteer Bitwarden ondersteuning" }, "sync": { "message": "Synchroniseren" @@ -3425,38 +3425,6 @@ "message": "In-/Uitklappen", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Gegevens importeren naar Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Je LastPass-gegevens beschermen en naar Bitwarden importeren?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Als niet-versleuteld bestand opslaan", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Naar Bitwarden importeren", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importeren…", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Gegevens succesvol geïmporteerd!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fout bij importeren. Controleer console voor meer informatie.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Netwerkfout opgetreden tijdens het importeren.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliasdomein" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index ad7d2582146..a7913bbfc9b 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index ad7d2582146..a7913bbfc9b 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index b14cd9961a0..b79acf95a1c 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -3425,38 +3425,6 @@ "message": "Zwiń/rozwiń", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Zaimportować Twoje dane do Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Ochronić Twoje dane LastPass i zaimportować do Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Zapisz jako niezaszyfrowany plik", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importuj do Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importowanie...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dane pomyślnie zaimportowane!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Błąd podczas importowania. Sprawdź konsolę, aby uzyskać szczegółowe informacje.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Wystąpił błąd sieci podczas importu.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Domena aliasu" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 3ac6412a3cd..0971c5e1bc5 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -3425,38 +3425,6 @@ "message": "Alternar colapso", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importar seus dados para o Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Proteja seus dados do LastPass e importe para o Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Salvar como arquivo não criptografado", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar para o Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importando...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dados importados com sucesso!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erro ao importar. Verifique o console para detalhes.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Erro de rede encontrado durante a importação.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias do domínio" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index f2f7cd23247..663e337d01c 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -3425,38 +3425,6 @@ "message": "Alternar colapso", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importar os seus dados para o Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Proteger os seus dados LastPass e importar para o Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Guardar como ficheiro não encriptado", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importar para o Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "A importar...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dados importados com sucesso!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Erro de importação. Verifique a consola para obter detalhes.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Erro de rede encontrado durante a importação.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias de domínio" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 04b3e02fc98..165c3749b53 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -3425,38 +3425,6 @@ "message": "Comutare restrângere", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 605f1bb9a49..5519737f16c 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -3425,38 +3425,6 @@ "message": "Свернуть/развернуть", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Импортировать данные в Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Защитить данные LastPass и импортировать их в Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Сохранить как незашифрованный файл", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Импортировать в Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Импорт...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Данные успешно импортированы!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Ошибка импорта. Проверьте консоль для получения подробной информации.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Во время импорта возникла сетевая ошибка.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Псевдоним домена" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 6a7bb304315..801e088aca8 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index e1020228e87..92d0e35a51c 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -3425,38 +3425,6 @@ "message": "Prepnúť zbalenie", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importovať údaje do Bitwardenu?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Ochrániť údaje z LastPassu a importovať ich do Bitwardenu?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Uložiť ako nezašifrovaný súbor", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importovať do Bitwardenu", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importovanie...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Import údajov prebehol úspešne!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Chyba pri importovaní. Podrobnosti nájdete v konzole.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Počas importu sa vyskytla chyba siete.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias doména" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index db8558b0a32..d57d6ec5e57 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 948956bb360..80de0ba3c8e 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -3425,38 +3425,6 @@ "message": "Промени проширење", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Увезите своје податке у Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Заштитите своје LastPass податке и увезите у Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Сачувати као нешифровану датотеку", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Увоз у Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Увоз...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Подаци су успешно увезени!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Грешка при увозу. Проверите конзолу за детаље.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Дошло је до грешке на мрежи током увоза.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Домен алијаса" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 47e9dc2cff6..1927f12bc27 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -3425,38 +3425,6 @@ "message": "Växla synlig/dold", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Importera din data till Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Skydda din LastPass-data och importera till Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Spara som okrypterad fil", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Importera till Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importerar...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data har importerats till ditt valv!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Fel vid importeringen. Kolla konsolen för detaljer.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Nätverksfel uppstod vid import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Aliasdomän" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index ad7d2582146..a7913bbfc9b 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index b3cddf4e83a..a8a2585f42f 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -3425,38 +3425,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Import your data to Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Save as unencrypted file", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Import to Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Importing...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Data successfully imported!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Error importing. Check console for details.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Network error encountered during import.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias domain" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 1d60fbed0e9..34ad0f14483 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -385,7 +385,7 @@ "message": "Hiç klasör eklenmedi" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "Kasanızdaki kayıtları organize etmek için klasörler oluşturun" }, "deleteFolderPermanently": { "message": "Bu klasörü kalıcı olarak silmek istediğinizden emin misiniz?" @@ -2337,7 +2337,7 @@ "message": "Bitwarden, oturum açmış tüm hesaplar için bu alan adlarının hesap bilgilerini kaydetmeyi sormayacaktır. Değişikliklerin etkili olması için sayfayı yenilemeniz gerekir." }, "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": "Bu siteler için otomatik doldurma ve diğer ilgili özellikler önerilmeyecektir. Değişikliklerin devreye girmesi için sayfayı yenilemelisiniz." }, "autofillBlockedNoticeV2": { "message": "Bu sitede otomatik doldurma engellenmiş." @@ -3425,38 +3425,6 @@ "message": "Daraltmayı aç/kapat", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Verileriniz Bitwarden'a aktarılsın mı?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "LastPass verileriniz korunsun ve Bitwarden'a aktarılsın mı?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Şifrelenmemiş dosya olarak kaydet", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Bitwarden'a aktar", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "İçe aktarılıyor...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Veriler başarıyla içe aktarıldı!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "İçe aktarma hatası. Ayrıntılar için konsolu kontrol edin.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "İçe aktarma sırasında ağ hatasıyla karşılaşıldı.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Alias alan adı" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 2d35489e09a..a1ec52ee80e 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -3425,38 +3425,6 @@ "message": "Згорнути/розгорнути", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Імпортувати ваші дані до Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Захистити ваші дані LastPass та імпортувати до Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Зберегти незашифрований файл", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Імпортувати до Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Триває імпортування...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Дані успішно імпортовано!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Помилка імпортування. Перевірте консоль для перегляду подробиць.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Під час імпортування сталася мережева помилка.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Псевдонім домену" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index af33914d3e7..b6668d8dc99 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -3425,38 +3425,6 @@ "message": "Bật/tắt thu gọn", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "Nhập dữ liệu của bạn vào Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "Bảo vệ dữ liệu LastPass của bạn và nhập vào Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "Lưu dưới dạng tập tin không được mã hóa", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "Nhập vào Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "Đang nhập...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "Dữ liệu đã được nhập thành công!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "Xảy ra lỗi trong quá trình nhập. Kiểm tra bảng điều khiển để biết thêm chi tiết.", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "Đã xảy ra lỗi mạng trong quá trình nhập dữ liệu.", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "Tên miền thay thế" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 14ad3927bec..8cd91d44a8a 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -3425,38 +3425,6 @@ "message": "切换折叠", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "导入您的数据到 Bitwarden 吗?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "保护您的 LastPass 数据并导入到 Bitwarden 吗?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "保存为未加密的文件", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "导入到 Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "正在导入...", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "数据成功导入!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "导入时出错。检查控制台以获取详细信息。", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "导入过程中遇到网络错误。", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "别名域" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index d5903198e36..13edf4920de 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2340,10 +2340,10 @@ "message": "自動填入及其它相關的功能無法在這些網站上使用。您必須重新整理頁面來使變更生效。" }, "autofillBlockedNoticeV2": { - "message": "Autofill is blocked for this website." + "message": "自動填入已於此網站封鎖" }, "autofillBlockedNoticeGuidance": { - "message": "Change this in settings" + "message": "您可以於設定中進行更改" }, "websiteItemLabel": { "message": "網站 $number$ (URI)", @@ -3425,38 +3425,6 @@ "message": "切換至折疊狀態", "description": "Toggling an expand/collapse state." }, - "filelessImport": { - "message": "匯入你的資料至 Bitwarden?", - "description": "Default notification title for triggering a fileless import." - }, - "lpFilelessImport": { - "message": "保護你的 LastPass 資料並匯入至 Bitwarden?", - "description": "LastPass specific notification title for triggering a fileless import." - }, - "lpCancelFilelessImport": { - "message": "儲存為未加密的檔案", - "description": "LastPass specific notification button text for cancelling a fileless import." - }, - "startFilelessImport": { - "message": "匯入至 Bitwarden", - "description": "Notification button text for starting a fileless import." - }, - "importing": { - "message": "匯入中……", - "description": "Notification message for when an import is in progress." - }, - "dataSuccessfullyImported": { - "message": "資料匯入成功!", - "description": "Notification message for when an import has completed successfully." - }, - "dataImportFailed": { - "message": "匯入時發生錯誤。檢查控制台以了解詳細資訊。", - "description": "Notification message for when an import has failed." - }, - "importNetworkError": { - "message": "匯入時遇到網路錯誤", - "description": "Notification message for when an import has failed due to a network error." - }, "aliasDomain": { "message": "別名網域" }, @@ -4008,10 +3976,10 @@ "message": "密碼金鑰已移除" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "自動填入建議" }, "itemSuggestions": { - "message": "Suggested items" + "message": "建議項目" }, "autofillSuggestionsTip": { "message": "對此網站儲存登入項目為自動填入" diff --git a/apps/browser/store/locales/ru/copy.resx b/apps/browser/store/locales/ru/copy.resx index f67462c7502..0da1e70f897 100644 --- a/apps/browser/store/locales/ru/copy.resx +++ b/apps/browser/store/locales/ru/copy.resx @@ -143,7 +143,7 @@ Дополнительные причины выбрать Bitwarden: Шифрование мирового класса -Пароли защищены усовершенствованным сквозным шифрованием (AES-256, хэштег salt и PBKDF2 SHA-256), поэтому ваши данные остаются в безопасности и конфиденциальности. +Пароли защищены усовершенствованным сквозным шифрованием (AES-256, использование salt и PBKDF2 SHA-256), поэтому ваши данные остаются в безопасности и конфиденциальности. Сторонние аудиты Bitwarden регулярно проводит комплексные сторонние аудиты безопасности с известными фирмами по безопасности. Эти ежегодные аудиты включают оценку исходного кода и тестирование на проникновение по IP-адресам Bitwarden, серверам и веб-приложениям. From 2438e6b934c8e1e26e8c84fe36cebe0d62366010 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:10:58 +0100 Subject: [PATCH 255/270] [deps] Platform: Update Rust crate pin-project to v1.1.8 (#12961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García --- apps/desktop/desktop_native/Cargo.lock | 8 ++++---- apps/desktop/desktop_native/core/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index fceef8e6e7a..f5783c63321 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -2102,18 +2102,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index cc407f09ec2..170967f555d 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -28,7 +28,7 @@ base64 = "=0.22.1" byteorder = "=1.5.0" cbc = { version = "=0.1.2", features = ["alloc"] } homedir = "=0.3.4" -pin-project = "=1.1.7" +pin-project = "=1.1.8" dirs = "=5.0.1" futures = "=0.3.31" interprocess = { version = "=2.2.1", features = ["tokio"] } From 0f60db3ce0612cd7edfb30b30cf49975e321e729 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:51:13 +0100 Subject: [PATCH 256/270] [deps] Architecture: Update eslint-config-prettier to v10 (#12975) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64d4cc7d993..d13354fcee0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -141,7 +141,7 @@ "electron-store": "8.2.0", "electron-updater": "6.3.9", "eslint": "8.57.1", - "eslint-config-prettier": "9.1.0", + "eslint-config-prettier": "10.0.1", "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", @@ -15945,13 +15945,13 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "bin/cli.js" + "eslint-config-prettier": "build/bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" diff --git a/package.json b/package.json index 9f58ed160bd..0d89c7e3991 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "electron-store": "8.2.0", "electron-updater": "6.3.9", "eslint": "8.57.1", - "eslint-config-prettier": "9.1.0", + "eslint-config-prettier": "10.0.1", "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", From 9d70ca36e9353fd7b74716155e76f2cae51e21c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:32:06 +0100 Subject: [PATCH 257/270] [deps] Platform: Update Rust crate windows-registry to v0.4.0 (#12970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García --- apps/desktop/desktop_native/Cargo.lock | 91 +++++++++++++++++++-- apps/desktop/desktop_native/napi/Cargo.toml | 2 +- 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index f5783c63321..b75b68e0b96 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -3407,13 +3407,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.2.0", - "windows-targets 0.52.6", + "windows-result 0.3.0", + "windows-strings 0.3.0", + "windows-targets 0.53.0", ] [[package]] @@ -3434,6 +3434,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34" +dependencies = [ + "windows-targets 0.53.0", +] + [[package]] name = "windows-strings" version = "0.1.0" @@ -3446,11 +3455,11 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978d65aedf914c664c510d9de43c8fd85ca745eaff1ed53edf409b479e441663" +checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] @@ -3504,13 +3513,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3523,6 +3548,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3535,6 +3566,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3547,12 +3584,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3565,6 +3614,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3577,6 +3632,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3589,6 +3650,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3601,6 +3668,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.6.22" diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index 8e19d62c1e6..974948e254b 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -27,7 +27,7 @@ tokio-util = "=0.7.12" tokio-stream = "=0.1.15" [target.'cfg(windows)'.dependencies] -windows-registry = "=0.3.0" +windows-registry = "=0.4.0" [build-dependencies] napi-build = "=2.1.4" From 1c7bbcfb2a75e64d2fb4017803829abea542803b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:36:31 +0000 Subject: [PATCH 258/270] [deps] AC: Update sass to v1.83.4 (#12952) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index d13354fcee0..9d2040476c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -166,7 +166,7 @@ "process": "0.11.10", "remark-gfm": "4.0.0", "rimraf": "6.0.1", - "sass": "1.83.1", + "sass": "1.83.4", "sass-loader": "16.0.4", "storybook": "8.4.7", "style-loader": "4.0.0", @@ -28494,9 +28494,9 @@ } }, "node_modules/sass": { - "version": "1.83.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.1.tgz", - "integrity": "sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==", + "version": "1.83.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.4.tgz", + "integrity": "sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==", "dev": true, "license": "MIT", "dependencies": { @@ -28579,13 +28579,13 @@ "license": "MIT" }, "node_modules/sass/node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", + "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.16.0" + "node": ">= 14.18.0" }, "funding": { "type": "individual", diff --git a/package.json b/package.json index 0d89c7e3991..48c2e03b129 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "process": "0.11.10", "remark-gfm": "4.0.0", "rimraf": "6.0.1", - "sass": "1.83.1", + "sass": "1.83.4", "sass-loader": "16.0.4", "storybook": "8.4.7", "style-loader": "4.0.0", From 833f88f9d9719db45f9a6adbd9b7f094c2981298 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:22:47 +0100 Subject: [PATCH 259/270] Autosync the updated translations (#12993) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 3 ++ apps/web/src/locales/ar/messages.json | 3 ++ apps/web/src/locales/az/messages.json | 3 ++ apps/web/src/locales/be/messages.json | 3 ++ apps/web/src/locales/bg/messages.json | 3 ++ apps/web/src/locales/bn/messages.json | 3 ++ apps/web/src/locales/bs/messages.json | 3 ++ apps/web/src/locales/ca/messages.json | 45 +++++++++++++----------- apps/web/src/locales/cs/messages.json | 3 ++ apps/web/src/locales/cy/messages.json | 3 ++ apps/web/src/locales/da/messages.json | 3 ++ apps/web/src/locales/de/messages.json | 3 ++ apps/web/src/locales/el/messages.json | 3 ++ apps/web/src/locales/en_GB/messages.json | 3 ++ apps/web/src/locales/en_IN/messages.json | 3 ++ apps/web/src/locales/eo/messages.json | 3 ++ apps/web/src/locales/es/messages.json | 3 ++ apps/web/src/locales/et/messages.json | 3 ++ apps/web/src/locales/eu/messages.json | 3 ++ apps/web/src/locales/fa/messages.json | 3 ++ apps/web/src/locales/fi/messages.json | 3 ++ apps/web/src/locales/fil/messages.json | 3 ++ apps/web/src/locales/fr/messages.json | 3 ++ apps/web/src/locales/gl/messages.json | 3 ++ apps/web/src/locales/he/messages.json | 3 ++ apps/web/src/locales/hi/messages.json | 3 ++ apps/web/src/locales/hr/messages.json | 3 ++ apps/web/src/locales/hu/messages.json | 3 ++ apps/web/src/locales/id/messages.json | 3 ++ apps/web/src/locales/it/messages.json | 3 ++ apps/web/src/locales/ja/messages.json | 3 ++ apps/web/src/locales/ka/messages.json | 3 ++ apps/web/src/locales/km/messages.json | 3 ++ apps/web/src/locales/kn/messages.json | 3 ++ apps/web/src/locales/ko/messages.json | 3 ++ apps/web/src/locales/lv/messages.json | 17 +++++---- apps/web/src/locales/ml/messages.json | 3 ++ apps/web/src/locales/mr/messages.json | 3 ++ apps/web/src/locales/my/messages.json | 3 ++ apps/web/src/locales/nb/messages.json | 3 ++ apps/web/src/locales/ne/messages.json | 3 ++ apps/web/src/locales/nl/messages.json | 3 ++ apps/web/src/locales/nn/messages.json | 3 ++ apps/web/src/locales/or/messages.json | 3 ++ apps/web/src/locales/pl/messages.json | 3 ++ apps/web/src/locales/pt_BR/messages.json | 3 ++ apps/web/src/locales/pt_PT/messages.json | 11 +++--- apps/web/src/locales/ro/messages.json | 3 ++ apps/web/src/locales/ru/messages.json | 5 ++- apps/web/src/locales/si/messages.json | 3 ++ apps/web/src/locales/sk/messages.json | 3 ++ apps/web/src/locales/sl/messages.json | 3 ++ apps/web/src/locales/sr/messages.json | 3 ++ apps/web/src/locales/sr_CS/messages.json | 3 ++ apps/web/src/locales/sv/messages.json | 3 ++ apps/web/src/locales/te/messages.json | 3 ++ apps/web/src/locales/th/messages.json | 3 ++ apps/web/src/locales/tr/messages.json | 3 ++ apps/web/src/locales/uk/messages.json | 3 ++ apps/web/src/locales/vi/messages.json | 3 ++ apps/web/src/locales/zh_CN/messages.json | 23 ++++++------ apps/web/src/locales/zh_TW/messages.json | 3 ++ 62 files changed, 229 insertions(+), 43 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 206ff27e9ba..4e1d2322af3 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index bc22f9e132a..f8db7b496cc 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index c7d98b5f41d..9cc946e7b1e 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Əlaqələndirilməmiş SSO." + }, "unlinkedSsoUser": { "message": "$ID$ istifadəçisi üçün SSO əlaqəsi kəsildi.", "placeholders": { diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 03d7bf3a984..02032485ce4 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Адлучаныя SSO для карыстальніка $ID$.", "placeholders": { diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index d8edea8adf7..e5f5a0ff266 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Еднократната идентификация е прекъсната." + }, "unlinkedSsoUser": { "message": "Самоличността $ID$ е извадено от еднократното вписване.", "placeholders": { diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 1fccb7fc32e..0b9fbc99609 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 01d11af8e1e..2e9d60fc4b2 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index b87c098363f..6693703f63b 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1,6 +1,6 @@ { "allApplications": { - "message": "All applications" + "message": "Totes les notificacions" }, "criticalApplications": { "message": "Critical applications" @@ -96,7 +96,7 @@ "message": "Apps marked as critical" }, "application": { - "message": "Application" + "message": "Aplicació" }, "atRiskPasswords": { "message": "At-risk passwords" @@ -193,7 +193,7 @@ "message": "Notes" }, "note": { - "message": "Note" + "message": "Nota" }, "customFields": { "message": "Camps personalitzats" @@ -205,16 +205,16 @@ "message": "Credencials d'inici de sessió" }, "personalDetails": { - "message": "Personal details" + "message": "Detalls personals" }, "identification": { - "message": "Identification" + "message": "Identificació" }, "contactInfo": { - "message": "Contact info" + "message": "Informació de contacte" }, "cardDetails": { - "message": "Card details" + "message": "Dades de la targeta" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -378,7 +378,7 @@ "message": "Dr." }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Targeta de crèdit caducada" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" @@ -825,7 +825,7 @@ "message": "Copy license number" }, "copyName": { - "message": "Copy name" + "message": "Copia el nom" }, "me": { "message": "Jo" @@ -1911,7 +1911,7 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "inici de sessió nou", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -2165,7 +2165,7 @@ "message": "," }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "o" }, "twoStepAuthenticatorInstructionSuffix": { "message": "." @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO sense enllaçar per a l'usuari $ID$.", "placeholders": { @@ -3875,7 +3878,7 @@ "message": "Login request has already expired." }, "justNow": { - "message": "Just now" + "message": "Ara mateix" }, "requestedXMinutesAgo": { "message": "Requested $MINUTES$ minutes ago", @@ -3890,7 +3893,7 @@ "message": "Creating account on" }, "checkYourEmail": { - "message": "Check your email" + "message": "Comprova el correu" }, "followTheLinkInTheEmailSentTo": { "message": "Follow the link in the email sent to" @@ -4452,7 +4455,7 @@ "message": "By continuing, you agree to the" }, "and": { - "message": "and" + "message": "i" }, "acceptPolicies": { "message": "Si activeu aquesta casella, indiqueu que esteu d’acord amb el següent:" @@ -4473,7 +4476,7 @@ "message": "Temps d'espera de la caixa forta" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Temps d'espera" }, "vaultTimeoutDesc": { "message": "Trieu quan es tancarà la vostra caixa forta i feu l'acció seleccionada." @@ -4542,7 +4545,7 @@ "message": "Seleccionat" }, "recommended": { - "message": "Recommended" + "message": "Recomanat" }, "ownership": { "message": "Propietat" @@ -5029,7 +5032,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "Copia l'enllaç" }, "copySendLink": { "message": "Copia l'enllaç Send", @@ -5535,7 +5538,7 @@ } }, "viewSend": { - "message": "View Send", + "message": "Veure Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -7937,7 +7940,7 @@ "message": "Càrrega de fitxers" }, "upload": { - "message": "Upload" + "message": "Puja" }, "acceptedFormats": { "message": "Formats acceptats:" @@ -7949,10 +7952,10 @@ "message": "o" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Desbloqueja amb dades biomètriques" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "Desbloqueja amb codi PIN" }, "unlockWithMasterPassword": { "message": "Unlock with master password" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 40edce811aa..30a7c523d2a 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Nepropojené SSO." + }, "unlinkedSsoUser": { "message": "Bylo zrušeno propojení SSO pro uživatele $ID$.", "placeholders": { diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 9e08bcd173e..b0002e385a9 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 5759e59ee1c..403636653cf 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Fjern SSO-tilknytning." + }, "unlinkedSsoUser": { "message": "Af-linket SSO for bruger $ID$.", "placeholders": { diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 2977a681004..89d49fe0c17 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "SSO-Verknüpfung aufgehoben." + }, "unlinkedSsoUser": { "message": "SSO-Verknüpfung für Benutzer $ID$ aufgehoben.", "placeholders": { diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 218d72c3e58..4e0b435f4ea 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Αποσυνδεδεμένο SSO για το χρήστη $ID$.", "placeholders": { diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index ece3d75e638..8b765b7a697 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index d9263876859..8dd3e4d599e 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index c3f767d0507..7651514110b 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 850eb82897c..863fba66ec6 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO desvinculado para el usuario $ID$.", "placeholders": { diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index d58b2ab26d4..84f6812d8dc 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Kasutaja $ID$ SSO on eemaldatud.", "placeholders": { diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 666bd261d5a..d7b5f91bc63 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "$ID$ erabiltzailearentzako SSO lotura kendua.", "placeholders": { diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index c7e6486cd67..7590cc7b545 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO برای کاربر $ID$ پیوند نخورده است.", "placeholders": { diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 6a30c6d08da..604e9ecadce 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Poisti kertakirjautumisen käyttäjältä \"$ID$\".", "placeholders": { diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 55574b772ad..095d5b05565 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Hindi naka-link na SSO para sa user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 869d39ae472..e7d2df4da50 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "SSO non lié." + }, "unlinkedSsoUser": { "message": "SSO dissocié pour l'utilisateur $ID$.", "placeholders": { diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 8ad001fa632..39cc3fd0841 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index f90c42a417e..bc831bf501c 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index ce3661942cd..1da5e61f6d1 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index f0f53ef2311..9f64a64fdc9 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Odspojen SSO za korisnika $ID$.", "placeholders": { diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 57520933201..d8d24fc43b8 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Az SSO szétkapcsolásra került." + }, "unlinkedSsoUser": { "message": "SSO szétkapcsolva $ID$ felhasználónál.", "placeholders": { diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 5e6d6570a44..1e796e4d36d 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO tidak ditautkan untuk pengguna $ID$.", "placeholders": { diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index b6a39905add..62a4044039d 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "SSO non collegato." + }, "unlinkedSsoUser": { "message": "SSO per l'utente $ID$ scollegato.", "placeholders": { diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index ad8310b98a2..e4255c93644 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "ユーザー $ID$ にリンクされていない SSO", "placeholders": { diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 8f44a4ca065..4c525c7f993 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 19f8bb82e54..bb7d3b657b5 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index cc19c636a09..de8bcbfd2c8 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "ಬಳಕೆದಾರ $ID$ ಗಾಗಿ ಲಿಂಕ್ ಮಾಡದ SSO.", "placeholders": { diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 5c06e0a2b13..c4122e12c28 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "$ID$ 사용자에 대한 SSO 연결이 해제되었습니다.", "placeholders": { diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 29d555004b8..48531c50d07 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "SSO atsaistīta." + }, "unlinkedSsoUser": { "message": "Atsaistīta vienotā pieteikšanās lietotājam $ID$.", "placeholders": { @@ -8269,31 +8272,31 @@ "message": "Uzticamās ierīces" }, "memberDecryptionOptionTdeDescPart1": { - "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "message": "Dalībniekiem nebūs nepieciešama galvenā parole, kad pieteiksies ar SSO. Galvenā parole ir aizvietota ar šifrēšanas atslēgu, kas tiek glabāta ierīcē, padarot to par uzticamu. Pirmā ierīce, kurā dalībnieks izveido savu kontu un tajā piesakās, būs uzticama. Jaunas ierīces būs nepieciešams apstiprināt ar esošu uzticamo ierīci vai pārvaldītājam. ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink1": { - "message": "single organization", + "message": "Vienas apvienības", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart2": { - "message": "policy,", + "message": "nosacījums,", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink2": { - "message": "SSO required", + "message": "SSO nepieciešamības", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart3": { - "message": "policy, and", + "message": "nosacījums un", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink3": { - "message": "account recovery administration", + "message": "kontu atkopšanas pārvaldības", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart4": { - "message": "policy will turn on when this option is used.", + "message": "nosacījums tiks ieslēgts, kad šī iespēja tiks izmantota.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index cd01e892e7e..00a28c35e2e 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 19f8bb82e54..bb7d3b657b5 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 19f8bb82e54..bb7d3b657b5 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 37c1c5224f5..020c208fda8 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Koblet fra SSO for brukeren $ID$.", "placeholders": { diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 2e95d72a392..54ca22dca15 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index c16534c7ce4..d1fb95104cb 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Ontkoppelde SSO." + }, "unlinkedSsoUser": { "message": "SSO ontkoppeld voor gebruiker $ID$.", "placeholders": { diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 1e8d1191ee1..09c74757eec 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 19f8bb82e54..bb7d3b657b5 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 63a2d8801c3..c4877b6b0f8 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Odłącz logowanie jednokrotne SSO dla użytkownika %$ID$.", "placeholders": { diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 842e6274ffd..13f7203246b 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO desvinculado para o usuário $ID$.", "placeholders": { diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 6a649f38ad2..14a5de13e79 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -3768,8 +3768,11 @@ } } }, + "unlinkedSso": { + "message": "SSO desvinculado." + }, "unlinkedSsoUser": { - "message": "SSO do utilizador $ID$ desligado.", + "message": "SSO desvinculado para o utilizador $ID$.", "placeholders": { "id": { "content": "$1", @@ -4904,13 +4907,13 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" }, "unlinkSso": { - "message": "Desativar SSO" + "message": "Desvincular SSO" }, "unlinkSsoConfirmation": { - "message": "Tem a certeza de que pretende desligar o SSO desta organização?" + "message": "Tem a certeza de que pretende desvincular o SSO desta organização?" }, "linkSso": { - "message": "Ativar SSO" + "message": "Vincular SSO" }, "singleOrg": { "message": "Organização única" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index f7bfbb0e592..a8a682b5cd4 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO deconectat pentru utilizatorul $ID$.", "placeholders": { diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 2e74ee4094c..e50556a8309 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Несвязанный SSO." + }, "unlinkedSsoUser": { "message": "Несвязанный SSO для пользователя $ID$.", "placeholders": { @@ -8269,7 +8272,7 @@ "message": "Доверенные устройства" }, "memberDecryptionOptionTdeDescPart1": { - "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "message": "Пользователям не понадобится мастер-пароль при входе в систему с SSO. Мастер-пароль заменяется ключом шифрования, хранящимся на устройстве, который делает его надёжным. Первое устройство, в которое участник создаёт свой аккаунт и входит в первый раз, будет доверенным. Новые устройства должны быть утверждены существующим доверенным устройством или администратором.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink1": { diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 1896f6938e9..a3453b43c74 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 2fdcc5b305a..027f732874e 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "SSO odpojené pre používateľa $ID$.", "placeholders": { diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 92981a60058..3325831a206 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 4429fda9683..a3d8ac72406 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Отповезај SSO за $ID$.", "placeholders": { diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index aa9a003027b..2e57f7b3523 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index ec4b495d305..8a93d6cb0e3 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Olänkad SSO för användare $ID$.", "placeholders": { diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 19f8bb82e54..bb7d3b657b5 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 16769d1110b..9003b4e2570 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 0e775f85d93..57078576569 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "SSO bağlantısı kesildi." + }, "unlinkedSsoUser": { "message": "$ID$ kullanıcısı için SSO bağlantısı kesildi.", "placeholders": { diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index a3d3904cbd9..5e72f2007cd 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Користувач $ID$ не має пов'язаного SSO.", "placeholders": { diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 5a4e81ad93e..17097c3d90c 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "Unlinked SSO for user $ID$.", "placeholders": { diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 1d37fc4c14a..e19d3965305 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -2864,10 +2864,10 @@ "message": "账单" }, "noUnpaidInvoices": { - "message": "没有未支付的账单。" + "message": "无未支付的账单。" }, "noPaidInvoices": { - "message": "没有已支付的账单。" + "message": "无已支付的账单。" }, "paid": { "message": "已支付", @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "为用户 $ID$ 取消链接 SSO。", "placeholders": { @@ -8269,31 +8272,31 @@ "message": "受信任设备" }, "memberDecryptionOptionTdeDescPart1": { - "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "message": "使用 SSO 登录时,成员无需主密码。主密码将被存储在设备上的加密密钥所取代,从而使该设备成为受信任设备。成员创建账户并登录的第一个设备将受到信任。新设备需要由现有受信任设备或管理员批准。", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink1": { - "message": "single organization", + "message": "单一组织", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart2": { - "message": "policy,", + "message": "策略,", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink2": { - "message": "SSO required", + "message": "要求 SSO", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart3": { - "message": "policy, and", + "message": "策略,以及", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink3": { - "message": "account recovery administration", + "message": "账户恢复管理", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart4": { - "message": "policy will turn on when this option is used.", + "message": "策略将在使用此选项时开启。", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { @@ -9183,7 +9186,7 @@ "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { - "message": "(跨域身份管理系统)(使用您的身份提供程序的实施指南)以自动向 Bitwarden 配置用户和群组。", + "message": "(跨域身份管理系统)以自动向 Bitwarden 配置用户和群组(使用您的身份提供程序的实施指南)。", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "bwdc": { diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 4f47a7865eb..4bb67a786a9 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -3768,6 +3768,9 @@ } } }, + "unlinkedSso": { + "message": "Unlinked SSO." + }, "unlinkedSsoUser": { "message": "已為使用者 $ID$ 取消連結 SSO。", "placeholders": { From b92a98110e40169d436fd04580ab764f10cafdb1 Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Tue, 21 Jan 2025 09:50:58 -0500 Subject: [PATCH 260/270] replace provider clients components with vNext implementation (#12934) --- .../providers/clients/clients.component.html | 110 +++++----- .../providers/clients/clients.component.ts | 111 +++++++--- .../clients/vnext-clients.component.html | 82 ------- .../clients/vnext-clients.component.ts | 167 --------------- .../providers/providers-routing.module.ts | 30 +-- .../providers/providers.module.ts | 6 - .../app/billing/providers/clients/index.ts | 1 - .../clients/manage-clients.component.html | 127 +++++------ .../clients/manage-clients.component.ts | 121 +++++++---- .../providers/clients/no-clients.component.ts | 3 + .../vnext-manage-clients.component.html | 83 -------- .../clients/vnext-manage-clients.component.ts | 201 ------------------ .../clients/vnext-no-clients.component.ts | 50 ----- libs/common/src/enums/feature-flag.enum.ts | 2 - 14 files changed, 277 insertions(+), 817 deletions(-) delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.html delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.html delete mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-no-clients.component.ts diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html index 220a2214600..e6b5ae122f3 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html @@ -1,5 +1,9 @@ - +
    {{ "newClient" | i18n }} @@ -24,63 +28,55 @@ {{ "loading" | i18n }} - -

    {{ "noClientsInList" | i18n }}

    - - +

    {{ "noClientsInList" | i18n }}

    + + - - - - - - - - - - - - - - - + + + + + + + + + + - - -
    {{ "name" | i18n }}{{ "numberOfUsers" | i18n }}{{ "billingPlan" | i18n }}
    - - - {{ o.organizationName }} - - {{ o.userCount }} - / {{ o.seats }} - - {{ o.plan }} - - {{ "name" | i18n }}{{ "numberOfUsers" | i18n }}{{ "billingPlan" | i18n }} + + + {{ row.organizationName }} + + {{ row.userCount }} + / {{ row.seats }} + + {{ row.plan }} + +
    +
    + + + diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts index 80ed3025306..f2f16585742 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts @@ -1,26 +1,35 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom, from, map } from "rxjs"; -import { switchMap, takeUntil } from "rxjs/operators"; +import { debounceTime, first, switchMap } from "rxjs/operators"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { PlanType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { + AvatarModule, + DialogService, + TableDataSource, + TableModule, + ToastService, +} from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { WebProviderService } from "../services/web-provider.service"; import { AddOrganizationComponent } from "./add-organization.component"; -import { BaseClientsComponent } from "./base-clients.component"; const DisallowedPlanTypes = [ PlanType.Free, @@ -32,13 +41,26 @@ const DisallowedPlanTypes = [ @Component({ templateUrl: "clients.component.html", + standalone: true, + imports: [ + SharedOrganizationModule, + HeaderModule, + CommonModule, + JslibModule, + AvatarModule, + RouterModule, + TableModule, + ], }) -export class ClientsComponent extends BaseClientsComponent implements OnInit, OnDestroy { - providerId: string; - addableOrganizations: Organization[]; +export class ClientsComponent { + providerId: string = ""; + addableOrganizations: Organization[] = []; loading = true; manageOrganizations = false; showAddExisting = false; + dataSource: TableDataSource = + new TableDataSource(); + protected searchControl = new FormControl("", { nonNullable: true }); constructor( private router: Router, @@ -46,28 +68,19 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On private apiService: ApiService, private organizationService: OrganizationService, private organizationApiService: OrganizationApiServiceAbstraction, - activatedRoute: ActivatedRoute, - dialogService: DialogService, - i18nService: I18nService, - searchService: SearchService, - toastService: ToastService, - validationService: ValidationService, - webProviderService: WebProviderService, + private activatedRoute: ActivatedRoute, + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + private validationService: ValidationService, + private webProviderService: WebProviderService, ) { - super( - activatedRoute, - dialogService, - i18nService, - searchService, - toastService, - validationService, - webProviderService, - ); - } + this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { + this.searchControl.setValue(queryParams.search); + }); - ngOnInit() { - this.activatedRoute.parent.params - .pipe( + this.activatedRoute.parent?.params + ?.pipe( switchMap((params) => { this.providerId = params.providerId; return this.providerService.get$(this.providerId).pipe( @@ -85,18 +98,46 @@ export class ClientsComponent extends BaseClientsComponent implements OnInit, On }), ); }), - takeUntil(this.destroy$), + takeUntilDestroyed(), ) .subscribe(); + + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((searchText) => { + this.dataSource.filter = (data) => + data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; + }); } - ngOnDestroy() { - super.ngOnDestroy(); + async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: organization.organizationName, + content: { key: "detachOrganizationConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.webProviderService.detachOrganization(this.providerId, organization.id); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("detachedOrganization", organization.organizationName), + }); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } } async load() { const response = await this.apiService.getProviderClients(this.providerId); - this.clients = response.data != null && response.data.length > 0 ? response.data : []; + const clients = response.data != null && response.data.length > 0 ? response.data : []; + this.dataSource.data = clients; this.manageOrganizations = (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; const candidateOrgs = (await this.organizationService.getAll()).filter( diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.html deleted file mode 100644 index e6b5ae122f3..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - {{ "newClient" | i18n }} - - - - - - - {{ "loading" | i18n }} - - - -

    {{ "noClientsInList" | i18n }}

    - - - - {{ "name" | i18n }} - {{ "numberOfUsers" | i18n }} - {{ "billingPlan" | i18n }} - - - - - - - - {{ row.organizationName }} - - - {{ row.userCount }} - / {{ row.seats }} - - - {{ row.plan }} - - - - - - - -
    diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts deleted file mode 100644 index 2be38477d4c..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/vnext-clients.component.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl } from "@angular/forms"; -import { ActivatedRoute, Router, RouterModule } from "@angular/router"; -import { firstValueFrom, from, map } from "rxjs"; -import { debounceTime, first, switchMap } from "rxjs/operators"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { PlanType } from "@bitwarden/common/billing/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { - AvatarModule, - DialogService, - TableDataSource, - TableModule, - ToastService, -} from "@bitwarden/components"; -import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; -import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; - -import { WebProviderService } from "../services/web-provider.service"; - -import { AddOrganizationComponent } from "./add-organization.component"; - -const DisallowedPlanTypes = [ - PlanType.Free, - PlanType.FamiliesAnnually2019, - PlanType.FamiliesAnnually, - PlanType.TeamsStarter2023, - PlanType.TeamsStarter, -]; - -@Component({ - templateUrl: "vnext-clients.component.html", - standalone: true, - imports: [ - SharedOrganizationModule, - HeaderModule, - CommonModule, - JslibModule, - AvatarModule, - RouterModule, - TableModule, - ], -}) -export class vNextClientsComponent { - providerId: string = ""; - addableOrganizations: Organization[] = []; - loading = true; - manageOrganizations = false; - showAddExisting = false; - dataSource: TableDataSource = - new TableDataSource(); - protected searchControl = new FormControl("", { nonNullable: true }); - - constructor( - private router: Router, - private providerService: ProviderService, - private apiService: ApiService, - private organizationService: OrganizationService, - private organizationApiService: OrganizationApiServiceAbstraction, - private activatedRoute: ActivatedRoute, - private dialogService: DialogService, - private i18nService: I18nService, - private toastService: ToastService, - private validationService: ValidationService, - private webProviderService: WebProviderService, - ) { - this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { - this.searchControl.setValue(queryParams.search); - }); - - this.activatedRoute.parent?.params - ?.pipe( - switchMap((params) => { - this.providerId = params.providerId; - return this.providerService.get$(this.providerId).pipe( - map((provider) => provider?.providerStatus === ProviderStatusType.Billable), - map((isBillable) => { - if (isBillable) { - return from( - this.router.navigate(["../manage-client-organizations"], { - relativeTo: this.activatedRoute, - }), - ); - } else { - return from(this.load()); - } - }), - ); - }), - takeUntilDestroyed(), - ) - .subscribe(); - - this.searchControl.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) - .subscribe((searchText) => { - this.dataSource.filter = (data) => - data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; - }); - } - - async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: organization.organizationName, - content: { key: "detachOrganizationConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - await this.webProviderService.detachOrganization(this.providerId, organization.id); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("detachedOrganization", organization.organizationName), - }); - await this.load(); - } catch (e) { - this.validationService.showError(e); - } - } - - async load() { - const response = await this.apiService.getProviderClients(this.providerId); - const clients = response.data != null && response.data.length > 0 ? response.data : []; - this.dataSource.data = clients; - this.manageOrganizations = - (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; - const candidateOrgs = (await this.organizationService.getAll()).filter( - (o) => o.isOwner && o.providerId == null, - ); - const allowedOrgsIds = await Promise.all( - candidateOrgs.map((o) => this.organizationApiService.get(o.id)), - ).then((orgs) => - orgs.filter((o) => !DisallowedPlanTypes.includes(o.planType)).map((o) => o.id), - ); - this.addableOrganizations = candidateOrgs.filter((o) => allowedOrgsIds.includes(o.id)); - - this.showAddExisting = this.addableOrganizations.length !== 0; - this.loading = false; - } - - async addExistingOrganization() { - const dialogRef = AddOrganizationComponent.open(this.dialogService, { - providerId: this.providerId, - organizations: this.addableOrganizations, - }); - - if (await firstValueFrom(dialogRef.closed)) { - await this.load(); - } - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index 09276263332..00c944e69bb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -2,10 +2,8 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; -import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component"; import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component"; @@ -14,12 +12,10 @@ import { ProviderSubscriptionComponent, hasConsolidatedBilling, ProviderBillingHistoryComponent, - vNextManageClientsComponent, } from "../../billing/providers"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; -import { vNextClientsComponent } from "./clients/vnext-clients.component"; import { providerPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { EventsComponent } from "./manage/events.component"; @@ -86,25 +82,13 @@ const routes: Routes = [ children: [ { path: "", pathMatch: "full", redirectTo: "clients" }, { path: "clients/create", component: CreateOrganizationComponent }, - ...featureFlaggedRoute({ - defaultComponent: ClientsComponent, - flaggedComponent: vNextClientsComponent, - featureFlag: FeatureFlag.PM12443RemovePagingLogic, - routeOptions: { - path: "clients", - data: { titleId: "clients" }, - }, - }), - ...featureFlaggedRoute({ - defaultComponent: ManageClientsComponent, - flaggedComponent: vNextManageClientsComponent, - featureFlag: FeatureFlag.PM12443RemovePagingLogic, - routeOptions: { - path: "manage-client-organizations", - data: { titleId: "clients" }, - canActivate: [hasConsolidatedBilling], - }, - }), + { path: "clients", component: ClientsComponent, data: { titleId: "clients" } }, + { + path: "manage-client-organizations", + canActivate: [hasConsolidatedBilling], + component: ManageClientsComponent, + data: { titleId: "clients" }, + }, { path: "manage", children: [ diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 80108e66eda..37cb9618b60 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -11,9 +11,7 @@ import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { CreateClientDialogComponent, - NoClientsComponent, ManageClientNameDialogComponent, - ManageClientsComponent, ManageClientSubscriptionDialogComponent, ProviderBillingHistoryComponent, ProviderSubscriptionComponent, @@ -21,7 +19,6 @@ import { } from "../../billing/providers"; import { AddOrganizationComponent } from "./clients/add-organization.component"; -import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component"; @@ -59,7 +56,6 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr AddOrganizationComponent, BulkConfirmDialogComponent, BulkRemoveDialogComponent, - ClientsComponent, CreateOrganizationComponent, EventsComponent, MembersComponent, @@ -68,8 +64,6 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr UserAddEditComponent, AddEditMemberDialogComponent, CreateClientDialogComponent, - NoClientsComponent, - ManageClientsComponent, ManageClientNameDialogComponent, ManageClientSubscriptionDialogComponent, ProviderBillingHistoryComponent, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts index 05887fc198e..898d51e0baf 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts @@ -3,5 +3,4 @@ export * from "./manage-clients.component"; export * from "./manage-client-name-dialog.component"; export * from "./manage-client-subscription-dialog.component"; export * from "./no-clients.component"; -export * from "./vnext-manage-clients.component"; export * from "./replace.pipe"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html index 49a9208107f..7c560e49579 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html @@ -1,5 +1,5 @@ - + {{ "addNewOrganization" | i18n }} @@ -16,78 +16,67 @@ - + - - {{ "client" | i18n }} - {{ "assigned" | i18n }} - {{ "used" | i18n }} - {{ "remaining" | i18n }} - {{ "billingPlan" | i18n }} - - + {{ "client" | i18n }} + {{ "assigned" | i18n }} + {{ "used" | i18n }} + {{ "remaining" | i18n }} + {{ "billingPlan" | i18n }} + - - - - - - - - - - {{ client.seats }} - - - {{ client.occupiedSeats }} - - - {{ client.remainingSeats }} - - - {{ removeMonthly(client.plan) }} - - - - - - - - - - + + + + + + + + + {{ row.seats }} + + + {{ row.occupiedSeats }} + + + {{ row.remainingSeats }} + + + {{ row.plan | replace: " (Monthly)" : "" }} + + + + + + + + + - -
    + +
    diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts index d413551b743..ee2c541e72f 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts @@ -1,23 +1,28 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, from, lastValueFrom, map } from "rxjs"; -import { switchMap } from "rxjs/operators"; +import { debounceTime, first, switchMap } from "rxjs/operators"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { + AvatarModule, + DialogService, + TableDataSource, + TableModule, + ToastService, +} from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; -import { BaseClientsComponent } from "../../../admin-console/providers/clients/base-clients.component"; import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; import { @@ -32,47 +37,53 @@ import { ManageClientSubscriptionDialogResultType, openManageClientSubscriptionDialog, } from "./manage-client-subscription-dialog.component"; +import { NoClientsComponent } from "./no-clients.component"; +import { ReplacePipe } from "./replace.pipe"; @Component({ templateUrl: "manage-clients.component.html", + standalone: true, + imports: [ + AvatarModule, + TableModule, + HeaderModule, + SharedOrganizationModule, + NoClientsComponent, + ReplacePipe, + ], }) -export class ManageClientsComponent extends BaseClientsComponent { - providerId: string; - provider: Provider; - +export class ManageClientsComponent { + providerId: string = ""; + provider: Provider | undefined; loading = true; isProviderAdmin = false; + dataSource: TableDataSource = + new TableDataSource(); - protected plans: PlanResponse[]; + protected searchControl = new FormControl("", { nonNullable: true }); + protected plans: PlanResponse[] = []; constructor( - private billingApiService: BillingApiService, + private billingApiService: BillingApiServiceAbstraction, private providerService: ProviderService, private router: Router, - activatedRoute: ActivatedRoute, - dialogService: DialogService, - i18nService: I18nService, - searchService: SearchService, - toastService: ToastService, - validationService: ValidationService, - webProviderService: WebProviderService, + private activatedRoute: ActivatedRoute, + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + private validationService: ValidationService, + private webProviderService: WebProviderService, ) { - super( - activatedRoute, - dialogService, - i18nService, - searchService, - toastService, - validationService, - webProviderService, - ); + this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { + this.searchControl.setValue(queryParams.search); + }); - this.activatedRoute.parent.params - .pipe( + this.activatedRoute.parent?.params + ?.pipe( switchMap((params) => { this.providerId = params.providerId; return this.providerService.get$(this.providerId).pipe( - map((provider) => provider?.providerStatus === ProviderStatusType.Billable), + map((provider: Provider) => provider?.providerStatus === ProviderStatusType.Billable), map((isBillable) => { if (!isBillable) { return from( @@ -89,20 +100,24 @@ export class ManageClientsComponent extends BaseClientsComponent { takeUntilDestroyed(), ) .subscribe(); - } - removeMonthly = (plan: string) => plan.replace(" (Monthly)", ""); + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((searchText) => { + this.dataSource.filter = (data) => + data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; + }); + } async load() { this.provider = await firstValueFrom(this.providerService.get$(this.providerId)); - this.isProviderAdmin = this.provider.type === ProviderUserType.ProviderAdmin; + this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin; - this.clients = ( - await this.billingApiService.getProviderClientOrganizations(this.providerId) - ).data; + const clients = (await this.billingApiService.getProviderClientOrganizations(this.providerId)) + .data; - this.dataSource.data = this.clients; + this.dataSource.data = clients; this.plans = (await this.billingApiService.getPlans()).data; @@ -131,7 +146,7 @@ export class ManageClientsComponent extends BaseClientsComponent { organization: { id: organization.id, name: organization.organizationName, - seats: organization.seats, + seats: organization.seats ? organization.seats : 0, }, }, }); @@ -149,7 +164,7 @@ export class ManageClientsComponent extends BaseClientsComponent { const dialogRef = openManageClientSubscriptionDialog(this.dialogService, { data: { organization, - provider: this.provider, + provider: this.provider!, }, }); @@ -159,4 +174,28 @@ export class ManageClientsComponent extends BaseClientsComponent { await this.load(); } }; + + async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: organization.organizationName, + content: { key: "detachOrganizationConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.webProviderService.detachOrganization(this.providerId, organization.id); + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("detachedOrganization", organization.organizationName), + }); + await this.load(); + } catch (e) { + this.validationService.showError(e); + } + } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts index 30ab444fe8b..7e4323d7603 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; import { svgIcon } from "@bitwarden/components"; +import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; const gearIcon = svgIcon` @@ -23,6 +24,8 @@ const gearIcon = svgIcon` @Component({ selector: "app-no-clients", + standalone: true, + imports: [SharedOrganizationModule], template: `

    {{ "noClients" | i18n }}

    diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.html deleted file mode 100644 index 7c560e49579..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - {{ "addNewOrganization" | i18n }} - - - - - - {{ "loading" | i18n }} - - - - - - {{ "client" | i18n }} - {{ "assigned" | i18n }} - {{ "used" | i18n }} - {{ "remaining" | i18n }} - {{ "billingPlan" | i18n }} - - - - - - - - - - - {{ row.seats }} - - - {{ row.occupiedSeats }} - - - {{ row.remainingSeats }} - - - {{ row.plan | replace: " (Monthly)" : "" }} - - - - - - - - - - - -
    - -
    -
    diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.ts deleted file mode 100644 index 94f615f0cee..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-manage-clients.component.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, from, lastValueFrom, map } from "rxjs"; -import { debounceTime, first, switchMap } from "rxjs/operators"; - -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; -import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { - AvatarModule, - DialogService, - TableDataSource, - TableModule, - ToastService, -} from "@bitwarden/components"; -import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; -import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; - -import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; - -import { - CreateClientDialogResultType, - openCreateClientDialog, -} from "./create-client-dialog.component"; -import { - ManageClientNameDialogResultType, - openManageClientNameDialog, -} from "./manage-client-name-dialog.component"; -import { - ManageClientSubscriptionDialogResultType, - openManageClientSubscriptionDialog, -} from "./manage-client-subscription-dialog.component"; -import { ReplacePipe } from "./replace.pipe"; -import { vNextNoClientsComponent } from "./vnext-no-clients.component"; - -@Component({ - templateUrl: "vnext-manage-clients.component.html", - standalone: true, - imports: [ - AvatarModule, - TableModule, - HeaderModule, - SharedOrganizationModule, - vNextNoClientsComponent, - ReplacePipe, - ], -}) -export class vNextManageClientsComponent { - providerId: string = ""; - provider: Provider | undefined; - loading = true; - isProviderAdmin = false; - dataSource: TableDataSource = - new TableDataSource(); - - protected searchControl = new FormControl("", { nonNullable: true }); - protected plans: PlanResponse[] = []; - - constructor( - private billingApiService: BillingApiServiceAbstraction, - private providerService: ProviderService, - private router: Router, - private activatedRoute: ActivatedRoute, - private dialogService: DialogService, - private i18nService: I18nService, - private toastService: ToastService, - private validationService: ValidationService, - private webProviderService: WebProviderService, - ) { - this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { - this.searchControl.setValue(queryParams.search); - }); - - this.activatedRoute.parent?.params - ?.pipe( - switchMap((params) => { - this.providerId = params.providerId; - return this.providerService.get$(this.providerId).pipe( - map((provider: Provider) => provider?.providerStatus === ProviderStatusType.Billable), - map((isBillable) => { - if (!isBillable) { - return from( - this.router.navigate(["../clients"], { - relativeTo: this.activatedRoute, - }), - ); - } else { - return from(this.load()); - } - }), - ); - }), - takeUntilDestroyed(), - ) - .subscribe(); - - this.searchControl.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) - .subscribe((searchText) => { - this.dataSource.filter = (data) => - data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; - }); - } - - async load() { - this.provider = await firstValueFrom(this.providerService.get$(this.providerId)); - - this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin; - - const clients = (await this.billingApiService.getProviderClientOrganizations(this.providerId)) - .data; - - this.dataSource.data = clients; - - this.plans = (await this.billingApiService.getPlans()).data; - - this.loading = false; - } - - createClient = async () => { - const reference = openCreateClientDialog(this.dialogService, { - data: { - providerId: this.providerId, - plans: this.plans, - }, - }); - - const result = await lastValueFrom(reference.closed); - - if (result === CreateClientDialogResultType.Submitted) { - await this.load(); - } - }; - - manageClientName = async (organization: ProviderOrganizationOrganizationDetailsResponse) => { - const dialogRef = openManageClientNameDialog(this.dialogService, { - data: { - providerId: this.providerId, - organization: { - id: organization.id, - name: organization.organizationName, - seats: organization.seats ? organization.seats : 0, - }, - }, - }); - - const result = await firstValueFrom(dialogRef.closed); - - if (result === ManageClientNameDialogResultType.Submitted) { - await this.load(); - } - }; - - manageClientSubscription = async ( - organization: ProviderOrganizationOrganizationDetailsResponse, - ) => { - const dialogRef = openManageClientSubscriptionDialog(this.dialogService, { - data: { - organization, - provider: this.provider!, - }, - }); - - const result = await firstValueFrom(dialogRef.closed); - - if (result === ManageClientSubscriptionDialogResultType.Submitted) { - await this.load(); - } - }; - - async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: organization.organizationName, - content: { key: "detachOrganizationConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - await this.webProviderService.detachOrganization(this.providerId, organization.id); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("detachedOrganization", organization.organizationName), - }); - await this.load(); - } catch (e) { - this.validationService.showError(e); - } - } -} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-no-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-no-clients.component.ts deleted file mode 100644 index 5ad19945c51..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/vnext-no-clients.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Component, EventEmitter, Input, Output } from "@angular/core"; - -import { svgIcon } from "@bitwarden/components"; -import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; - -const gearIcon = svgIcon` - - - - - - - - - - - - - - - - -`; - -@Component({ - selector: "app-no-clients", - standalone: true, - imports: [SharedOrganizationModule], - template: `
    - -

    {{ "noClients" | i18n }}

    - - - {{ "addNewOrganization" | i18n }} - -
    `, -}) -export class vNextNoClientsComponent { - icon = gearIcon; - @Input() showAddOrganizationButton = true; - @Output() addNewOrganizationClicked = new EventEmitter(); - - addNewOrganization = () => this.addNewOrganizationClicked.emit(); -} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 2ac4f507cdb..c44947ed77b 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -43,7 +43,6 @@ export enum FeatureFlag { NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", MacOsNativeCredentialSync = "macos-native-credential-sync", - PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", PrivateKeyRegeneration = "pm-12241-private-key-regeneration", ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs", } @@ -99,7 +98,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, - [FeatureFlag.PM12443RemovePagingLogic]: FALSE, [FeatureFlag.PrivateKeyRegeneration]: FALSE, [FeatureFlag.ResellerManagedOrgAlert]: FALSE, } satisfies Record; From 4066022e18da5deba1a6acd9f888f65bdc288352 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:01:48 +0100 Subject: [PATCH 261/270] make the pricing breakdown to show properly (#12982) --- .../change-plan-dialog.component.html | 38 +++++++++---------- .../change-plan-dialog.component.ts | 3 ++ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 89c0075ba0d..96679ea1753 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -130,7 +130,7 @@ : selectableProduct.PasswordManager.seatPrice ) | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -400,7 +400,7 @@ : selectedPlan.PasswordManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -427,7 +427,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -444,7 +444,7 @@ {{ "additionalStorageGbMessage" | i18n }} × {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalStorageTotal(selectedPlan) | currency: "$" }}

    @@ -485,7 +485,7 @@ : selectedPlan.SecretsManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }}

    {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -524,7 +524,7 @@ {{ "serviceAccounts" | i18n | lowercase }} × {{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -580,7 +580,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} @@ -596,7 +596,7 @@ {{ "additionalStorageGbMessage" | i18n }} × {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ storageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" @@ -656,7 +656,7 @@ {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} @@ -675,7 +675,7 @@ {{ "serviceAccounts" | i18n | lowercase }} × {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -725,7 +725,7 @@ : selectedPlan.SecretsManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }}

    {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -764,7 +764,7 @@ {{ "serviceAccounts" | i18n }} × {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -786,7 +786,7 @@ : selectedPlan.PasswordManager.basePrice ) | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -813,7 +813,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "year" | i18n }} + /{{ selectedPlanInterval | i18n }} @@ -860,7 +860,7 @@ {{ "members" | i18n }} × {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} @@ -879,7 +879,7 @@ {{ "serviceAccounts" | i18n }} × {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    @@ -914,7 +914,7 @@ {{ "members" | i18n }} × {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ "month" | i18n }} + /{{ selectedPlanInterval | i18n }} {{ "freeForOneYear" | i18n }} 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 73577c7b002..4e850edfb0e 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 @@ -485,6 +485,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get selectedPlanInterval() { + if (this.isSubscriptionCanceled) { + return this.currentPlan.isAnnual ? "year" : "month"; + } return this.selectedPlan.isAnnual ? "year" : "month"; } From ecb0d1e2f37d60993f44e0f358e9b2f36f359a8d Mon Sep 17 00:00:00 2001 From: MarsCandyBars <63179967+MarsCandyBars@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:47:21 -0600 Subject: [PATCH 262/270] Adding break-words to imported filename (#12915) --- libs/importer/src/components/import.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 25172f850b7..c53a1d3f522 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -409,7 +409,7 @@
    {{ "selectImportFile" | i18n }} -
    +
    From eb99eba284b081db1662db3edb0078989c040de8 Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Tue, 21 Jan 2025 11:16:32 -0500 Subject: [PATCH 263/270] =?UTF-8?q?refactor(email-verification-feature-fla?= =?UTF-8?q?g):=20[PM-7882]=20Email=20Verificati=E2=80=A6=20(#12718)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(email-verification-feature-flag): [PM-7882] Email Verification - Removed email feature flag. --- .../src/auth/popup/home.component.html | 6 +-- apps/browser/src/auth/popup/home.component.ts | 14 +++---- .../src/auth/popup/login-v1.component.ts | 6 --- apps/browser/src/popup/app-routing.module.ts | 7 +--- apps/desktop/src/app/app-routing.module.ts | 7 +--- .../src/auth/login/login-v1.component.html | 2 +- .../src/auth/login/login-v1.component.ts | 9 +--- .../accept-family-sponsorship.component.ts | 32 ++++---------- ...families-for-enterprise-setup.component.ts | 9 ++-- .../accept/accept-emergency.component.ts | 26 +++--------- .../app/auth/login/login-v1.component.html | 5 ++- .../src/app/auth/login/login-v1.component.ts | 17 ++------ .../accept-organization.component.ts | 29 ++++--------- .../src/app/common/base.accept.component.ts | 6 +-- apps/web/src/app/oss-routing.module.ts | 42 ++++++++----------- .../src/app/tools/send/access.component.ts | 7 ---- .../send/send-access-explainer.component.html | 2 +- .../send/send-access-explainer.component.ts | 6 +-- .../manage/accept-provider.component.ts | 27 ++++-------- .../src/auth/components/login-v1.component.ts | 18 +++----- .../src/auth/components/sso.component.ts | 11 +---- .../src/services/jslib-services.module.ts | 6 --- .../login-secondary-content.component.ts | 7 +--- libs/auth/src/angular/sso/sso.component.ts | 11 +---- libs/auth/src/common/services/index.ts | 1 - .../common/services/register-route.service.ts | 21 ---------- libs/common/src/enums/feature-flag.enum.ts | 2 - 27 files changed, 84 insertions(+), 252 deletions(-) delete mode 100644 libs/auth/src/common/services/register-route.service.ts diff --git a/apps/browser/src/auth/popup/home.component.html b/apps/browser/src/auth/popup/home.component.html index ed395797961..08043cf88bb 100644 --- a/apps/browser/src/auth/popup/home.component.html +++ b/apps/browser/src/auth/popup/home.component.html @@ -1,4 +1,4 @@ - +
    diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts index 6060ac77abe..dbdcf7d5aa8 100644 --- a/apps/browser/src/auth/popup/home.component.ts +++ b/apps/browser/src/auth/popup/home.component.ts @@ -6,7 +6,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { Subject, firstValueFrom, switchMap, takeUntil, tap } from "rxjs"; import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; -import { LoginEmailServiceAbstraction, RegisterRouteService } from "@bitwarden/auth/common"; +import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common"; 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"; @@ -30,9 +30,6 @@ export class HomeComponent implements OnInit, OnDestroy { rememberEmail: [false], }); - // TODO: remove when email verification flag is removed - registerRoute$ = this.registerRouteService.registerRoute$(); - constructor( protected platformUtilsService: PlatformUtilsService, private formBuilder: FormBuilder, @@ -40,7 +37,6 @@ export class HomeComponent implements OnInit, OnDestroy { private i18nService: I18nService, private loginEmailService: LoginEmailServiceAbstraction, private accountSwitcherService: AccountSwitcherService, - private registerRouteService: RegisterRouteService, private toastService: ToastService, private configService: ConfigService, private route: ActivatedRoute, @@ -118,13 +114,15 @@ export class HomeComponent implements OnInit, OnDestroy { } await this.setLoginEmailValues(); - await this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } }); + await this.router.navigate(["login"], { + queryParams: { email: this.formGroup.controls.email.value }, + }); } async setLoginEmailValues() { // Note: Browser saves email settings here instead of the login component - this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail); - await this.loginEmailService.setLoginEmail(this.formGroup.value.email); + this.loginEmailService.setRememberEmail(this.formGroup.controls.rememberEmail.value); + await this.loginEmailService.setLoginEmail(this.formGroup.controls.email.value); await this.loginEmailService.saveEmailSettings(); } } diff --git a/apps/browser/src/auth/popup/login-v1.component.ts b/apps/browser/src/auth/popup/login-v1.component.ts index cb6380d62ed..b2c52f248c6 100644 --- a/apps/browser/src/auth/popup/login-v1.component.ts +++ b/apps/browser/src/auth/popup/login-v1.component.ts @@ -10,11 +10,9 @@ import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstrac import { LoginStrategyServiceAbstraction, LoginEmailServiceAbstraction, - RegisterRouteService, } from "@bitwarden/auth/common"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -51,8 +49,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit { route: ActivatedRoute, loginEmailService: LoginEmailServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, - webAuthnLoginService: WebAuthnLoginServiceAbstraction, - registerRouteService: RegisterRouteService, toastService: ToastService, ) { super( @@ -73,8 +69,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit { route, loginEmailService, ssoLoginService, - webAuthnLoginService, - registerRouteService, toastService, ); this.onSuccessfulLogin = async () => { diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index d55bebfa0c3..b831eef0baa 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -16,7 +16,6 @@ import { tdeDecryptionRequiredGuard, unauthGuardFn, } from "@bitwarden/angular/auth/guards"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { @@ -41,7 +40,6 @@ import { SsoComponent, TwoFactorTimeoutIcon, } from "@bitwarden/auth/angular"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { LockComponent } from "@bitwarden/key-management/angular"; import { NewDeviceVerificationNoticePageOneComponent, @@ -559,7 +557,7 @@ const routes: Routes = [ children: [ { path: "signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { elevation: 1, pageIcon: RegistrationUserAddIcon, @@ -585,7 +583,7 @@ const routes: Routes = [ }, { path: "finish-signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationLockAltIcon, elevation: 1, @@ -635,7 +633,6 @@ const routes: Routes = [ children: [ { path: "set-password-jit", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification)], component: SetPasswordJitComponent, data: { pageTitle: { diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index e565681de93..66fed8bcf28 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -14,7 +14,6 @@ import { tdeDecryptionRequiredGuard, unauthGuardFn, } from "@bitwarden/angular/auth/guards"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { @@ -39,7 +38,6 @@ import { SsoComponent, TwoFactorTimeoutIcon, } from "@bitwarden/auth/angular"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { LockComponent } from "@bitwarden/key-management/angular"; import { NewDeviceVerificationNoticePageOneComponent, @@ -330,7 +328,7 @@ const routes: Routes = [ children: [ { path: "signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationUserAddIcon, pageTitle: { @@ -354,7 +352,7 @@ const routes: Routes = [ }, { path: "finish-signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationLockAltIcon, } satisfies AnonLayoutWrapperData, @@ -384,7 +382,6 @@ const routes: Routes = [ }, { path: "set-password-jit", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification)], component: SetPasswordJitComponent, data: { pageTitle: { diff --git a/apps/desktop/src/auth/login/login-v1.component.html b/apps/desktop/src/auth/login/login-v1.component.html index efec43ffa86..aae4cec239d 100644 --- a/apps/desktop/src/auth/login/login-v1.component.html +++ b/apps/desktop/src/auth/login/login-v1.component.html @@ -50,7 +50,7 @@

    {{ "newAroundHere" | i18n }}

    -
    diff --git a/apps/desktop/src/auth/login/login-v1.component.ts b/apps/desktop/src/auth/login/login-v1.component.ts index e0c3f794dba..f78bee7323d 100644 --- a/apps/desktop/src/auth/login/login-v1.component.ts +++ b/apps/desktop/src/auth/login/login-v1.component.ts @@ -11,11 +11,9 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { LoginStrategyServiceAbstraction, LoginEmailServiceAbstraction, - RegisterRouteService, } from "@bitwarden/auth/common"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -77,8 +75,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe route: ActivatedRoute, loginEmailService: LoginEmailServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, - webAuthnLoginService: WebAuthnLoginServiceAbstraction, - registerRouteService: RegisterRouteService, toastService: ToastService, private configService: ConfigService, ) { @@ -100,8 +96,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe route, loginEmailService, ssoLoginService, - webAuthnLoginService, - registerRouteService, toastService, ); this.onSuccessfulLogin = () => { @@ -228,7 +222,7 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe } // Save off email for SSO - await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); + await this.ssoLoginService.setSsoEmail(this.formGroup.controls.email.value); // Generate necessary sso params const passwordOptions: any = { @@ -247,6 +241,7 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe // Save sso params await this.ssoLoginService.setSsoState(state); await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); + try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); // FIXME: Remove when updating file. Eslint update diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts index fad5da9d476..bef0589df83 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -2,7 +2,6 @@ // @ts-strict-ignore import { Component, inject } from "@angular/core"; import { Params } from "@angular/router"; -import { firstValueFrom } from "rxjs"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { OrganizationSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/organization-sponsorship.response"; @@ -39,31 +38,18 @@ export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { if (!qParams.register) { await this.router.navigate(["/login"], { queryParams: { email: qParams.email } }); } else { - // TODO: update logic when email verification flag is removed - let queryParams: Params; - let registerRoute = await firstValueFrom(this.registerRoute$); - if (registerRoute === "/register") { - queryParams = { - email: qParams.email, - }; - } else if (registerRoute === "/signup") { - // We have to override the base component route as we don't need users to - // complete email verification if they are coming directly an emailed invite. + // We don't need users to complete email verification if they are coming directly from an emailed invite. + // Therefore, we skip /signup and navigate directly to /finish-signup. - // TODO: in the future, to allow users to enter a name, consider sending all invite users to - // start registration page with prefilled email and a named token to be passed directly - // along to the finish-signup page without requiring email verification as - // we can treat the existence of the token as a form of email verification. - - registerRoute = "/finish-signup"; - queryParams = { + // TODO: in the future, to allow users to enter a name, consider sending all invite users to + // start registration page with prefilled email and a named token to be passed directly + // along to the finish-signup page without requiring email verification as + // we can treat the existence of the token as a form of email verification. + await this.router.navigate(["/finish-signup"], { + queryParams: { email: qParams.email, orgSponsoredFreeFamilyPlanToken: qParams.token, - }; - } - - await this.router.navigate([registerRoute], { - queryParams: queryParams, + }, }); } } diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index 031bd7bf180..d2d2f98d0a3 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -48,19 +48,18 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { loading = true; badToken = false; - token: string; - existingFamilyOrganizations: Organization[]; - existingFamilyOrganizations$: Observable; + token!: string; + existingFamilyOrganizations$!: Observable; showNewOrganization = false; - _organizationPlansComponent: OrganizationPlansComponent; - preValidateSponsorshipResponse: PreValidateSponsorshipResponse; + preValidateSponsorshipResponse!: PreValidateSponsorshipResponse; _selectedFamilyOrganizationId = ""; private _destroy = new Subject(); formGroup = this.formBuilder.group({ selectedFamilyOrganizationId: ["", Validators.required], }); + constructor( private router: Router, private platformUtilsService: PlatformUtilsService, diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts index 9d4bf654bf1..b3f635aee92 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts @@ -2,9 +2,7 @@ // @ts-strict-ignore import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -34,10 +32,9 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { i18nService: I18nService, route: ActivatedRoute, authService: AuthService, - registerRouteService: RegisterRouteService, private emergencyAccessService: EmergencyAccessService, ) { - super(router, platformUtilsService, i18nService, route, authService, registerRouteService); + super(router, platformUtilsService, i18nService, route, authService); } async authedHandler(qParams: Params): Promise { @@ -71,25 +68,14 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { } async register() { - let queryParams: Params; - let registerRoute = await firstValueFrom(this.registerRoute$); - if (registerRoute === "/register") { - queryParams = { - email: this.email, - }; - } else if (registerRoute === "/signup") { - // We have to override the base component route as we don't need users to - // complete email verification if they are coming directly an emailed invite. - registerRoute = "/finish-signup"; - queryParams = { + // We don't need users to complete email verification if they are coming directly from an emailed invite. + // Therefore, we skip /signup and navigate directly to /finish-signup. + await this.router.navigate(["/finish-signup"], { + queryParams: { email: this.email, acceptEmergencyAccessInviteToken: this.acceptEmergencyAccessInviteToken, emergencyAccessId: this.emergencyAccessId, - }; - } - - await this.router.navigate([registerRoute], { - queryParams: queryParams, + }, }); } } diff --git a/apps/web/src/app/auth/login/login-v1.component.html b/apps/web/src/app/auth/login/login-v1.component.html index 5b3c2a99424..b41e55a03b0 100644 --- a/apps/web/src/app/auth/login/login-v1.component.html +++ b/apps/web/src/app/auth/login/login-v1.component.html @@ -57,11 +57,12 @@ --> {{ "createAccount" | i18n }} + {{ "createAccount" | i18n }} +

    diff --git a/apps/web/src/app/auth/login/login-v1.component.ts b/apps/web/src/app/auth/login/login-v1.component.ts index 4a72612fd6e..a3099d991d9 100644 --- a/apps/web/src/app/auth/login/login-v1.component.ts +++ b/apps/web/src/app/auth/login/login-v1.component.ts @@ -3,7 +3,7 @@ import { Component, NgZone, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, takeUntil } from "rxjs"; +import { takeUntil } from "rxjs"; import { first } from "rxjs/operators"; import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component"; @@ -11,7 +11,6 @@ import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstrac import { LoginStrategyServiceAbstraction, LoginEmailServiceAbstraction, - RegisterRouteService, } from "@bitwarden/auth/common"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -20,7 +19,6 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -71,8 +69,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit { formValidationErrorService: FormValidationErrorsService, loginEmailService: LoginEmailServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, - webAuthnLoginService: WebAuthnLoginServiceAbstraction, - registerRouteService: RegisterRouteService, toastService: ToastService, ) { super( @@ -93,8 +89,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit { route, loginEmailService, ssoLoginService, - webAuthnLoginService, - registerRouteService, toastService, ); this.onSuccessfulLoginNavigate = this.goAfterLogIn; @@ -111,7 +105,7 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit { async ngOnInit() { // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - // If there is an query parameter called 'org', set previousUrl to `/create-organization?org=paramValue` + // If there is a query parameter called 'org', set previousUrl to `/create-organization?org=paramValue` if (qParams.org != null) { const route = this.router.createUrlTree(["create-organization"], { queryParams: { plan: qParams.org }, @@ -178,17 +172,14 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit { } async goToRegister() { - // TODO: remove when email verification flag is removed - const registerRoute = await firstValueFrom(this.registerRoute$); - if (this.emailFormControl.valid) { - await this.router.navigate([registerRoute], { + await this.router.navigate(["/signup"], { queryParams: { email: this.emailFormControl.value }, }); return; } - await this.router.navigate([registerRoute]); + await this.router.navigate(["/signup"]); } protected override async handleMigrateEncryptionKey(result: AuthResult): Promise { diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index 660b2759988..197b4031998 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -2,9 +2,7 @@ // @ts-strict-ignore import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -27,10 +25,9 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { i18nService: I18nService, route: ActivatedRoute, authService: AuthService, - registerRouteService: RegisterRouteService, private acceptOrganizationInviteService: AcceptOrganizationInviteService, ) { - super(router, platformUtilsService, i18nService, route, authService, registerRouteService); + super(router, platformUtilsService, i18nService, route, authService); } async authedHandler(qParams: Params): Promise { @@ -55,6 +52,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { async unauthedHandler(qParams: Params): Promise { const invite = OrganizationInvite.fromParams(qParams); + await this.acceptOrganizationInviteService.setOrganizationInvitation(invite); await this.navigateInviteAcceptance(invite); } @@ -86,25 +84,12 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent { // if SSO is disabled OR if sso is enabled but the SSO login required policy is not enabled // then send user to create account - // TODO: update logic when email verification flag is removed - let queryParams: Params; - let registerRoute = await firstValueFrom(this.registerRoute$); - if (registerRoute === "/register") { - queryParams = { - fromOrgInvite: "true", + // We don't need users to complete email verification if they are coming directly from an emailed invite. + // Therefore, we skip /signup and navigate directly to /finish-signup. + await this.router.navigate(["/finish-signup"], { + queryParams: { email: invite.email, - }; - } else if (registerRoute === "/signup") { - // We have to override the base component route as we don't need users to complete email verification - // if they are coming directly from an emailed org invite. - registerRoute = "/finish-signup"; - queryParams = { - email: invite.email, - }; - } - - await this.router.navigate([registerRoute], { - queryParams: queryParams, + }, }); return; } diff --git a/apps/web/src/app/common/base.accept.component.ts b/apps/web/src/app/common/base.accept.component.ts index 4e938fcd081..82a192ab0cf 100644 --- a/apps/web/src/app/common/base.accept.component.ts +++ b/apps/web/src/app/common/base.accept.component.ts @@ -5,7 +5,6 @@ import { ActivatedRoute, Params, Router } from "@angular/router"; import { Subject, firstValueFrom } from "rxjs"; import { first, switchMap, takeUntil } from "rxjs/operators"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -22,9 +21,6 @@ export abstract class BaseAcceptComponent implements OnInit { protected failedShortMessage = "inviteAcceptFailedShort"; protected failedMessage = "inviteAcceptFailed"; - // TODO: remove when email verification flag is removed - registerRoute$ = this.registerRouteService.registerRoute$(); - private destroy$ = new Subject(); constructor( @@ -33,10 +29,10 @@ export abstract class BaseAcceptComponent implements OnInit { protected i18nService: I18nService, protected route: ActivatedRoute, protected authService: AuthService, - protected registerRouteService: RegisterRouteService, ) {} abstract authedHandler(qParams: Params): Promise; + abstract unauthedHandler(qParams: Params): Promise; async ngOnInit() { diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index fadcc28f832..8a678a3b045 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -10,8 +10,8 @@ import { tdeDecryptionRequiredGuard, unauthGuardFn, } from "@bitwarden/angular/auth/guards"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap"; +import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, @@ -38,7 +38,6 @@ import { VaultIcon, LoginDecryptionOptionsComponent, } from "@bitwarden/auth/angular"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { LockComponent } from "@bitwarden/key-management/angular"; import { NewDeviceVerificationNoticePageOneComponent, @@ -46,7 +45,6 @@ import { VaultIcons, } from "@bitwarden/vault"; -import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap"; import { flagEnabled, Flags } from "../utils/flags"; import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component"; @@ -71,7 +69,6 @@ import { SecurityRoutingModule } from "./auth/settings/security/security-routing import { SsoComponentV1 } from "./auth/sso-v1.component"; import { CompleteTrialInitiationComponent } from "./auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; import { freeTrialTextResolver } from "./auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; -import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component"; import { TwoFactorAuthComponent } from "./auth/two-factor-auth.component"; import { TwoFactorComponent } from "./auth/two-factor.component"; import { UpdatePasswordComponent } from "./auth/update-password.component"; @@ -96,6 +93,18 @@ import { SendComponent } from "./tools/send/send.component"; import { VaultModule } from "./vault/individual-vault/vault.module"; const routes: Routes = [ + // These need to be placed at the top of the list prior to the root + // so that the redirectGuard does not interrupt the navigation. + { + path: "register", + redirectTo: "signup", + pathMatch: "full", + }, + { + path: "trial", + redirectTo: "signup", + pathMatch: "full", + }, { path: "", component: FrontendLayoutComponent, @@ -112,20 +121,6 @@ const routes: Routes = [ component: LoginViaWebAuthnComponent, data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties, }, - { - path: "register", - component: TrialInitiationComponent, - canActivate: [ - canAccessFeature(FeatureFlag.EmailVerification, false, "/signup", false), - unauthGuardFn(), - ], - data: { titleId: "createAccount" } satisfies RouteDataProperties, - }, - { - path: "trial", - redirectTo: "register", - pathMatch: "full", - }, { path: "set-password", component: SetPasswordComponent, @@ -347,7 +342,7 @@ const routes: Routes = [ children: [ { path: "signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationUserAddIcon, pageTitle: { @@ -372,7 +367,7 @@ const routes: Routes = [ }, { path: "finish-signup", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationLockAltIcon, titleId: "setAStrongPassword", @@ -406,7 +401,6 @@ const routes: Routes = [ }, { path: "set-password-jit", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification)], component: SetPasswordJitComponent, data: { pageTitle: { @@ -419,7 +413,7 @@ const routes: Routes = [ }, { path: "signup-link-expired", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], data: { pageIcon: RegistrationExpiredLinkIcon, pageTitle: { @@ -656,7 +650,7 @@ const routes: Routes = [ }, { path: "trial-initiation", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], component: CompleteTrialInitiationComponent, resolve: { pageTitle: freeTrialTextResolver, @@ -667,7 +661,7 @@ const routes: Routes = [ }, { path: "secrets-manager-trial-initiation", - canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + canActivate: [unauthGuardFn()], component: CompleteTrialInitiationComponent, resolve: { pageTitle: freeTrialTextResolver, diff --git a/apps/web/src/app/tools/send/access.component.ts b/apps/web/src/app/tools/send/access.component.ts index 80439acd510..ccfecdd197e 100644 --- a/apps/web/src/app/tools/send/access.component.ts +++ b/apps/web/src/app/tools/send/access.component.ts @@ -5,9 +5,7 @@ import { FormBuilder } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -58,9 +56,6 @@ export class AccessComponent implements OnInit { protected formGroup = this.formBuilder.group({}); - // TODO: remove when email verification flag is removed - registerRoute$ = this.registerRouteService.registerRoute$(); - private id: string; private key: string; @@ -71,8 +66,6 @@ export class AccessComponent implements OnInit { private sendApiService: SendApiService, private toastService: ToastService, private i18nService: I18nService, - private configService: ConfigService, - private registerRouteService: RegisterRouteService, private layoutWrapperDataService: AnonLayoutWrapperDataService, protected formBuilder: FormBuilder, ) {} diff --git a/apps/web/src/app/tools/send/send-access-explainer.component.html b/apps/web/src/app/tools/send/send-access-explainer.component.html index e8090cb850c..f56b68cc299 100644 --- a/apps/web/src/app/tools/send/send-access-explainer.component.html +++ b/apps/web/src/app/tools/send/send-access-explainer.component.html @@ -10,7 +10,7 @@ >Bitwarden Send {{ "sendAccessTaglineOr" | i18n }} - {{ + {{ "sendAccessTaglineSignUp" | i18n }} {{ "sendAccessTaglineTryToday" | i18n }} diff --git a/apps/web/src/app/tools/send/send-access-explainer.component.ts b/apps/web/src/app/tools/send/send-access-explainer.component.ts index 756a1068985..e7106110a65 100644 --- a/apps/web/src/app/tools/send/send-access-explainer.component.ts +++ b/apps/web/src/app/tools/send/send-access-explainer.component.ts @@ -1,7 +1,5 @@ import { Component } from "@angular/core"; -import { RegisterRouteService } from "@bitwarden/auth/common"; - import { SharedModule } from "../../shared"; @Component({ @@ -11,7 +9,5 @@ import { SharedModule } from "../../shared"; imports: [SharedModule], }) export class SendAccessExplainerComponent { - // TODO: remove when email verification flag is removed - registerRoute$ = this.registerRouteService.registerRoute$(); - constructor(private registerRouteService: RegisterRouteService) {} + constructor() {} } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts index bd8c54c39d7..56584b67f02 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts @@ -2,9 +2,7 @@ // @ts-strict-ignore import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ProviderUserAcceptRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-accept.request"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -33,9 +31,8 @@ export class AcceptProviderComponent extends BaseAcceptComponent { authService: AuthService, private apiService: ApiService, platformUtilService: PlatformUtilsService, - registerRouteService: RegisterRouteService, ) { - super(router, platformUtilService, i18nService, route, authService, registerRouteService); + super(router, platformUtilService, i18nService, route, authService); } async authedHandler(qParams: Params) { @@ -47,6 +44,7 @@ export class AcceptProviderComponent extends BaseAcceptComponent { qParams.providerUserId, request, ); + this.platformUtilService.showToast( "success", this.i18nService.t("inviteAccepted"), @@ -64,25 +62,14 @@ export class AcceptProviderComponent extends BaseAcceptComponent { } async register() { - let queryParams: Params; - let registerRoute = await firstValueFrom(this.registerRoute$); - if (registerRoute === "/register") { - queryParams = { - email: this.email, - }; - } else if (registerRoute === "/signup") { - // We have to override the base component route as we don't need users to - // complete email verification if they are coming directly an emailed invite. - registerRoute = "/finish-signup"; - queryParams = { + // We don't need users to complete email verification if they are coming directly from an emailed invite. + // Therefore, we skip /signup and navigate directly to /finish-signup. + await this.router.navigate(["/finish-signup"], { + queryParams: { email: this.email, providerUserId: this.providerUserId, providerInviteToken: this.providerInviteToken, - }; - } - - await this.router.navigate([registerRoute], { - queryParams: queryParams, + }, }); } } diff --git a/libs/angular/src/auth/components/login-v1.component.ts b/libs/angular/src/auth/components/login-v1.component.ts index ffe1dda3aed..3416901da97 100644 --- a/libs/angular/src/auth/components/login-v1.component.ts +++ b/libs/angular/src/auth/components/login-v1.component.ts @@ -10,11 +10,9 @@ import { LoginStrategyServiceAbstraction, LoginEmailServiceAbstraction, PasswordLoginCredentials, - RegisterRouteService, } from "@bitwarden/auth/common"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; @@ -56,7 +54,7 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni return this.formGroup.controls.email; } - formGroup = this.formBuilder.group({ + formGroup = this.formBuilder.nonNullable.group({ email: ["", [Validators.required, Validators.email]], masterPassword: [ "", @@ -67,14 +65,12 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni protected twoFactorRoute = "2fa"; protected successRoute = "vault"; - // TODO: remove when email verification flag is removed - protected registerRoute$ = this.registerRouteService.registerRoute$(); protected forcePasswordResetRoute = "update-temp-password"; protected destroy$ = new Subject(); get loggedEmail() { - return this.formGroup.value.email; + return this.formGroup.controls.email.value; } constructor( @@ -95,8 +91,6 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni protected route: ActivatedRoute, protected loginEmailService: LoginEmailServiceAbstraction, protected ssoLoginService: SsoLoginServiceAbstraction, - protected webAuthnLoginService: WebAuthnLoginServiceAbstraction, - protected registerRouteService: RegisterRouteService, protected toastService: ToastService, ) { super(environmentService, i18nService, platformUtilsService, toastService); @@ -146,8 +140,6 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni } async submit(showToast = true) { - const data = this.formGroup.value; - await this.setupCaptcha(); this.formGroup.markAllAsTouched(); @@ -170,10 +162,10 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni try { const credentials = new PasswordLoginCredentials( - data.email, - data.masterPassword, + this.formGroup.controls.email.value, + this.formGroup.controls.masterPassword.value, this.captchaToken, - null, + undefined, ); this.formPromise = this.loginStrategyService.logIn(credentials); diff --git a/libs/angular/src/auth/components/sso.component.ts b/libs/angular/src/auth/components/sso.component.ts index 8b4df78175f..6c13809566a 100644 --- a/libs/angular/src/auth/components/sso.component.ts +++ b/libs/angular/src/auth/components/sso.component.ts @@ -19,7 +19,6 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -50,7 +49,7 @@ export class SsoComponent implements OnInit { protected twoFactorRoute = "2fa"; protected successRoute = "lock"; protected trustedDeviceEncRoute = "login-initiated"; - protected changePasswordRoute = "set-password"; + protected changePasswordRoute = "set-password-jit"; protected forcePasswordResetRoute = "update-temp-password"; protected clientId: string; protected redirectUri: string; @@ -339,14 +338,6 @@ export class SsoComponent implements OnInit { } private async handleChangePasswordRequired(orgIdentifier: string) { - const emailVerification = await this.configService.getFeatureFlag( - FeatureFlag.EmailVerification, - ); - - if (emailVerification) { - this.changePasswordRoute = "set-password-jit"; - } - await this.navigateViaCallbackOrRoute( this.onSuccessfulLoginChangePasswordNavigate, [this.changePasswordRoute], diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index c1d25407c0e..7b6123e2589 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -35,7 +35,6 @@ import { UserDecryptionOptionsService, UserDecryptionOptionsServiceAbstraction, LogoutReason, - RegisterRouteService, AuthRequestApiService, DefaultAuthRequestApiService, DefaultLoginSuccessHandlerService, @@ -1354,11 +1353,6 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultServerSettingsService, deps: [ConfigService], }), - safeProvider({ - provide: RegisterRouteService, - useClass: RegisterRouteService, - deps: [ConfigService], - }), safeProvider({ provide: AnonLayoutWrapperDataService, useClass: DefaultAnonLayoutWrapperDataService, diff --git a/libs/auth/src/angular/login/login-secondary-content.component.ts b/libs/auth/src/angular/login/login-secondary-content.component.ts index dbc9535e67a..b608542b375 100644 --- a/libs/auth/src/angular/login/login-secondary-content.component.ts +++ b/libs/auth/src/angular/login/login-secondary-content.component.ts @@ -3,7 +3,6 @@ import { Component, inject } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { RegisterRouteService } from "@bitwarden/auth/common"; import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service"; import { LinkModule } from "@bitwarden/components"; @@ -13,16 +12,12 @@ import { LinkModule } from "@bitwarden/components"; template: ` `, }) export class LoginSecondaryContentComponent { - registerRouteService = inject(RegisterRouteService); serverSettingsService = inject(DefaultServerSettingsService); - // TODO: remove when email verification flag is removed - protected registerRoute$ = this.registerRouteService.registerRoute$(); - protected isUserRegistrationDisabled$ = this.serverSettingsService.isUserRegistrationDisabled$; } diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index 3bcc56ae4cd..4583332cb88 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -480,16 +480,7 @@ export class SsoComponent implements OnInit { } private async handleChangePasswordRequired(orgIdentifier: string) { - const emailVerification = await this.configService.getFeatureFlag( - FeatureFlag.EmailVerification, - ); - - let route = "set-password"; - if (emailVerification) { - route = "set-password-jit"; - } - - await this.router.navigate([route], { + await this.router.navigate(["set-password-jit"], { queryParams: { identifier: orgIdentifier, }, diff --git a/libs/auth/src/common/services/index.ts b/libs/auth/src/common/services/index.ts index d1cedebcf36..44f6afa5d23 100644 --- a/libs/auth/src/common/services/index.ts +++ b/libs/auth/src/common/services/index.ts @@ -4,6 +4,5 @@ export * from "./login-strategies/login-strategy.service"; export * from "./user-decryption-options/user-decryption-options.service"; export * from "./auth-request/auth-request.service"; export * from "./auth-request/auth-request-api.service"; -export * from "./register-route.service"; export * from "./accounts/lock.service"; export * from "./login-success-handler/default-login-success-handler.service"; diff --git a/libs/auth/src/common/services/register-route.service.ts b/libs/auth/src/common/services/register-route.service.ts deleted file mode 100644 index 5bc09db699e..00000000000 --- a/libs/auth/src/common/services/register-route.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Observable, map } from "rxjs"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -// This is a temporary service to determine the correct route to use for registration based on the email verification feature flag. -export class RegisterRouteService { - constructor(private configService: ConfigService) {} - - registerRoute$(): Observable { - return this.configService.getFeatureFlag$(FeatureFlag.EmailVerification).pipe( - map((emailVerificationEnabled) => { - if (emailVerificationEnabled) { - return "/signup"; - } else { - return "/register"; - } - }), - ); - } -} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index c44947ed77b..95d174dd2ad 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -23,7 +23,6 @@ export enum FeatureFlag { ExtensionRefresh = "extension-refresh", PersistPopupView = "persist-popup-view", PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", - EmailVerification = "email-verification", TwoFactorComponentRefactor = "two-factor-component-refactor", ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", VaultBulkManagementAction = "vault-bulk-management-action", @@ -78,7 +77,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.ExtensionRefresh]: FALSE, [FeatureFlag.PersistPopupView]: FALSE, [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, - [FeatureFlag.EmailVerification]: FALSE, [FeatureFlag.TwoFactorComponentRefactor]: FALSE, [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, [FeatureFlag.VaultBulkManagementAction]: FALSE, From 1b9f5461393b2194f988aeeacf8f78d1c749036a Mon Sep 17 00:00:00 2001 From: Andy Pixley <3723676+pixman20@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:24:00 -0500 Subject: [PATCH 264/270] [BRE-563] Reverting out electron_publish option. Needs to always happen (#12997) --- .github/workflows/publish-desktop.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index a8719f71a66..af24083e973 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -22,11 +22,6 @@ on: required: true default: '10' type: string - electron_publish: - description: 'Publish Electron blob to AWS' - required: true - default: true - type: boolean snap_publish: description: 'Publish to Snap store' required: true @@ -111,7 +106,6 @@ jobs: name: Electron blob publish runs-on: ubuntu-22.04 needs: setup - if: inputs.electron_publish env: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} From 5e1d5bad07ceac57213b0df8787b70898283247a Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Tue, 21 Jan 2025 09:50:50 -0800 Subject: [PATCH 265/270] [PM-14416] Risk Insights - Initial security task service (#12446) * [PM-14416] Add initial SecurityTask models and enums * [PM-14416] Add support for PATCH request method and 204 No Content response * [PM-14416] Add initial task service abstraction * [PM-14416] Add SecurityTask state/key definitions * [PM-14416] Add DefaultTaskService implementation * [PM-14416] Add DefaultTaskService tests * [PM-14416] Add better null checking to new models * [PM-14416] Improve null value filtering for task service --- libs/common/src/abstractions/api.service.ts | 4 +- .../src/platform/state/state-definitions.ts | 1 + libs/common/src/services/api.service.ts | 6 +- libs/common/src/types/guid.ts | 1 + libs/vault/src/index.ts | 2 + .../src/tasks/abstractions/task.service.ts | 45 +++ libs/vault/src/tasks/enums/index.ts | 2 + .../tasks/enums/security-task-status.enum.ts | 11 + .../tasks/enums/security-task-type.enum.ts | 6 + libs/vault/src/tasks/index.ts | 5 + libs/vault/src/tasks/models/index.ts | 1 + .../src/tasks/models/security-task.data.ts | 34 +++ .../tasks/models/security-task.response.ts | 28 ++ libs/vault/src/tasks/models/security-task.ts | 28 ++ .../services/default-task.service.spec.ts | 261 ++++++++++++++++++ .../tasks/services/default-task.service.ts | 93 +++++++ .../src/tasks/state/security-task.state.ts | 14 + libs/vault/src/utils/observable-utilities.ts | 37 +++ 18 files changed, 574 insertions(+), 5 deletions(-) create mode 100644 libs/vault/src/tasks/abstractions/task.service.ts create mode 100644 libs/vault/src/tasks/enums/index.ts create mode 100644 libs/vault/src/tasks/enums/security-task-status.enum.ts create mode 100644 libs/vault/src/tasks/enums/security-task-type.enum.ts create mode 100644 libs/vault/src/tasks/index.ts create mode 100644 libs/vault/src/tasks/models/index.ts create mode 100644 libs/vault/src/tasks/models/security-task.data.ts create mode 100644 libs/vault/src/tasks/models/security-task.response.ts create mode 100644 libs/vault/src/tasks/models/security-task.ts create mode 100644 libs/vault/src/tasks/services/default-task.service.spec.ts create mode 100644 libs/vault/src/tasks/services/default-task.service.ts create mode 100644 libs/vault/src/tasks/state/security-task.state.ts create mode 100644 libs/vault/src/utils/observable-utilities.ts diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 4b3dd335d15..daccc4bd16e 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -1,9 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { - CollectionRequest, CollectionAccessDetailsResponse, CollectionDetailsResponse, + CollectionRequest, CollectionResponse, } from "@bitwarden/admin-console/common"; @@ -136,7 +136,7 @@ import { OptionalCipherResponse } from "../vault/models/response/optional-cipher */ export abstract class ApiService { send: ( - method: "GET" | "POST" | "PUT" | "DELETE", + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body: any, authed: boolean, diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 483a8c050d3..c83119d9ad4 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -187,3 +187,4 @@ export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition( }, ); export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); +export const SECURITY_TASKS_DISK = new StateDefinition("securityTasks", "disk"); diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 7269f6e86fd..f40745142d0 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -3,9 +3,9 @@ import { firstValueFrom } from "rxjs"; import { - CollectionRequest, CollectionAccessDetailsResponse, CollectionDetailsResponse, + CollectionRequest, CollectionResponse, } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; @@ -1829,7 +1829,7 @@ export class ApiService implements ApiServiceAbstraction { } async send( - method: "GET" | "POST" | "PUT" | "DELETE", + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, body: any, authed: boolean, @@ -1869,7 +1869,7 @@ export class ApiService implements ApiServiceAbstraction { return responseJson; } else if (hasResponse && response.status === 200 && responseIsCsv) { return await response.text(); - } else if (response.status !== 200) { + } else if (response.status !== 200 && response.status !== 204) { const error = await this.handleError(response, false, authed); return Promise.reject(error); } diff --git a/libs/common/src/types/guid.ts b/libs/common/src/types/guid.ts index 59dbe28f907..5ad498c115a 100644 --- a/libs/common/src/types/guid.ts +++ b/libs/common/src/types/guid.ts @@ -10,3 +10,4 @@ export type PolicyId = Opaque; export type CipherId = Opaque; export type SendId = Opaque; export type IndexedEntityId = Opaque; +export type SecurityTaskId = Opaque; diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index c9a719934ac..23143fa2309 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -19,3 +19,5 @@ export { NewDeviceVerificationNoticePageTwoComponent } from "./components/new-de export { DecryptionFailureDialogComponent } from "./components/decryption-failure-dialog/decryption-failure-dialog.component"; export * as VaultIcons from "./icons"; + +export * from "./tasks"; diff --git a/libs/vault/src/tasks/abstractions/task.service.ts b/libs/vault/src/tasks/abstractions/task.service.ts new file mode 100644 index 00000000000..3d6ae72900f --- /dev/null +++ b/libs/vault/src/tasks/abstractions/task.service.ts @@ -0,0 +1,45 @@ +import { Observable } from "rxjs"; + +import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid"; +import { SecurityTask } from "@bitwarden/vault"; + +export abstract class TaskService { + /** + * Observable indicating if tasks are enabled for a given user. + * + * @remarks Internally, this checks the user's organization details to determine if tasks are enabled. + * @param userId + */ + abstract tasksEnabled$(userId: UserId): Observable; + + /** + * Observable of all tasks for a given user. + * @param userId + */ + abstract tasks$(userId: UserId): Observable; + + /** + * Observable of pending tasks for a given user. + * @param userId + */ + abstract pendingTasks$(userId: UserId): Observable; + + /** + * Retrieves tasks from the API for a given user and updates the local state. + * @param userId + */ + abstract refreshTasks(userId: UserId): Promise; + + /** + * Clears all the tasks from state for the given user. + * @param userId + */ + abstract clear(userId: UserId): Promise; + + /** + * Marks a task as complete in local state and updates the server. + * @param taskId - The ID of the task to mark as complete. + * @param userId - The user who is completing the task. + */ + abstract markAsComplete(taskId: SecurityTaskId, userId: UserId): Promise; +} diff --git a/libs/vault/src/tasks/enums/index.ts b/libs/vault/src/tasks/enums/index.ts new file mode 100644 index 00000000000..c1a5e81d877 --- /dev/null +++ b/libs/vault/src/tasks/enums/index.ts @@ -0,0 +1,2 @@ +export * from "./security-task-status.enum"; +export * from "./security-task-type.enum"; diff --git a/libs/vault/src/tasks/enums/security-task-status.enum.ts b/libs/vault/src/tasks/enums/security-task-status.enum.ts new file mode 100644 index 00000000000..1c6e7decc20 --- /dev/null +++ b/libs/vault/src/tasks/enums/security-task-status.enum.ts @@ -0,0 +1,11 @@ +export enum SecurityTaskStatus { + /** + * Default status for newly created tasks that have not been completed. + */ + Pending = 0, + + /** + * Status when a task is considered complete and has no remaining actions + */ + Completed = 1, +} diff --git a/libs/vault/src/tasks/enums/security-task-type.enum.ts b/libs/vault/src/tasks/enums/security-task-type.enum.ts new file mode 100644 index 00000000000..264cd88696b --- /dev/null +++ b/libs/vault/src/tasks/enums/security-task-type.enum.ts @@ -0,0 +1,6 @@ +export enum SecurityTaskType { + /** + * Task to update a cipher's password that was found to be at-risk by an administrator + */ + UpdateAtRiskCredential = 0, +} diff --git a/libs/vault/src/tasks/index.ts b/libs/vault/src/tasks/index.ts new file mode 100644 index 00000000000..a0339c6d8f4 --- /dev/null +++ b/libs/vault/src/tasks/index.ts @@ -0,0 +1,5 @@ +export * from "./enums"; +export * from "./models"; + +export * from "./abstractions/task.service"; +export * from "./services/default-task.service"; diff --git a/libs/vault/src/tasks/models/index.ts b/libs/vault/src/tasks/models/index.ts new file mode 100644 index 00000000000..7e31c136629 --- /dev/null +++ b/libs/vault/src/tasks/models/index.ts @@ -0,0 +1 @@ +export * from "./security-task"; diff --git a/libs/vault/src/tasks/models/security-task.data.ts b/libs/vault/src/tasks/models/security-task.data.ts new file mode 100644 index 00000000000..e2d9cc76c0a --- /dev/null +++ b/libs/vault/src/tasks/models/security-task.data.ts @@ -0,0 +1,34 @@ +import { Jsonify } from "type-fest"; + +import { CipherId, OrganizationId, SecurityTaskId } from "@bitwarden/common/types/guid"; + +import { SecurityTaskStatus, SecurityTaskType } from "../enums"; + +import { SecurityTaskResponse } from "./security-task.response"; + +export class SecurityTaskData { + id: SecurityTaskId; + organizationId: OrganizationId; + cipherId: CipherId | undefined; + type: SecurityTaskType; + status: SecurityTaskStatus; + creationDate: Date; + revisionDate: Date; + + constructor(response: SecurityTaskResponse) { + this.id = response.id; + this.organizationId = response.organizationId; + this.cipherId = response.cipherId; + this.type = response.type; + this.status = response.status; + this.creationDate = response.creationDate; + this.revisionDate = response.revisionDate; + } + + static fromJSON(obj: Jsonify) { + return Object.assign(new SecurityTaskData({} as SecurityTaskResponse), obj, { + creationDate: new Date(obj.creationDate), + revisionDate: new Date(obj.revisionDate), + }); + } +} diff --git a/libs/vault/src/tasks/models/security-task.response.ts b/libs/vault/src/tasks/models/security-task.response.ts new file mode 100644 index 00000000000..2a335eb5d2f --- /dev/null +++ b/libs/vault/src/tasks/models/security-task.response.ts @@ -0,0 +1,28 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { CipherId, OrganizationId, SecurityTaskId } from "@bitwarden/common/types/guid"; + +import { SecurityTaskStatus, SecurityTaskType } from "../enums"; + +export class SecurityTaskResponse extends BaseResponse { + id: SecurityTaskId; + organizationId: OrganizationId; + /** + * Optional cipherId for tasks that are related to a cipher. + */ + cipherId: CipherId | undefined; + type: SecurityTaskType; + status: SecurityTaskStatus; + creationDate: Date; + revisionDate: Date; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty("Id"); + this.organizationId = this.getResponseProperty("OrganizationId"); + this.cipherId = this.getResponseProperty("CipherId") || undefined; + this.type = this.getResponseProperty("Type"); + this.status = this.getResponseProperty("Status"); + this.creationDate = this.getResponseProperty("CreationDate"); + this.revisionDate = this.getResponseProperty("RevisionDate"); + } +} diff --git a/libs/vault/src/tasks/models/security-task.ts b/libs/vault/src/tasks/models/security-task.ts new file mode 100644 index 00000000000..42adb951945 --- /dev/null +++ b/libs/vault/src/tasks/models/security-task.ts @@ -0,0 +1,28 @@ +import { CipherId, OrganizationId, SecurityTaskId } from "@bitwarden/common/types/guid"; + +import { SecurityTaskStatus, SecurityTaskType } from "../enums"; + +import { SecurityTaskData } from "./security-task.data"; + +export class SecurityTask { + id: SecurityTaskId; + organizationId: OrganizationId; + /** + * Optional cipherId for tasks that are related to a cipher. + */ + cipherId: CipherId | undefined; + type: SecurityTaskType; + status: SecurityTaskStatus; + creationDate: Date; + revisionDate: Date; + + constructor(obj: SecurityTaskData) { + this.id = obj.id; + this.organizationId = obj.organizationId; + this.cipherId = obj.cipherId; + this.type = obj.type; + this.status = obj.status; + this.creationDate = obj.creationDate; + this.revisionDate = obj.revisionDate; + } +} diff --git a/libs/vault/src/tasks/services/default-task.service.spec.ts b/libs/vault/src/tasks/services/default-task.service.spec.ts new file mode 100644 index 00000000000..850d0bcc2b8 --- /dev/null +++ b/libs/vault/src/tasks/services/default-task.service.spec.ts @@ -0,0 +1,261 @@ +import { TestBed } from "@angular/core/testing"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid"; +import { DefaultTaskService, SecurityTaskStatus } from "@bitwarden/vault"; + +import { FakeStateProvider, mockAccountServiceWith } from "../../../../common/spec"; +import { SecurityTaskData } from "../models/security-task.data"; +import { SecurityTaskResponse } from "../models/security-task.response"; +import { SECURITY_TASKS } from "../state/security-task.state"; + +describe("Default task service", () => { + let fakeStateProvider: FakeStateProvider; + + const mockApiSend = jest.fn(); + const mockGetAllOrgs$ = jest.fn(); + + let testBed: TestBed; + + beforeEach(async () => { + mockApiSend.mockClear(); + mockGetAllOrgs$.mockClear(); + + fakeStateProvider = new FakeStateProvider(mockAccountServiceWith("user-id" as UserId)); + testBed = TestBed.configureTestingModule({ + imports: [], + providers: [ + DefaultTaskService, + { + provide: StateProvider, + useValue: fakeStateProvider, + }, + { + provide: ApiService, + useValue: { + send: mockApiSend, + }, + }, + { + provide: OrganizationService, + useValue: { + getAll$: mockGetAllOrgs$, + }, + }, + ], + }); + }); + + describe("tasksEnabled$", () => { + it("should emit true if any organization uses risk insights", async () => { + mockGetAllOrgs$.mockReturnValue( + new BehaviorSubject([ + { + useRiskInsights: false, + }, + { + useRiskInsights: true, + }, + ] as Organization[]), + ); + + const { tasksEnabled$ } = testBed.inject(DefaultTaskService); + + const result = await firstValueFrom(tasksEnabled$("user-id" as UserId)); + + expect(result).toBe(true); + }); + + it("should emit false if no organization uses risk insights", async () => { + mockGetAllOrgs$.mockReturnValue( + new BehaviorSubject([ + { + useRiskInsights: false, + }, + { + useRiskInsights: false, + }, + ] as Organization[]), + ); + + const { tasksEnabled$ } = testBed.inject(DefaultTaskService); + + const result = await firstValueFrom(tasksEnabled$("user-id" as UserId)); + + expect(result).toBe(false); + }); + }); + + describe("tasks$", () => { + it("should fetch tasks from the API when the state is null", async () => { + mockApiSend.mockResolvedValue({ + data: [ + { + id: "task-id", + }, + ] as SecurityTaskResponse[], + }); + + fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, null); + + const { tasks$ } = testBed.inject(DefaultTaskService); + + const result = await firstValueFrom(tasks$("user-id" as UserId)); + + expect(result.length).toBe(1); + expect(mockApiSend).toHaveBeenCalledWith("GET", "/tasks", null, true, true); + }); + + it("should use the tasks from state when not null", async () => { + fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, [ + { + id: "task-id" as SecurityTaskId, + } as SecurityTaskData, + ]); + + const { tasks$ } = testBed.inject(DefaultTaskService); + + const result = await firstValueFrom(tasks$("user-id" as UserId)); + + expect(result.length).toBe(1); + expect(mockApiSend).not.toHaveBeenCalled(); + }); + + it("should share the same observable for the same user", async () => { + const { tasks$ } = testBed.inject(DefaultTaskService); + + const first = tasks$("user-id" as UserId); + const second = tasks$("user-id" as UserId); + + expect(first).toBe(second); + }); + }); + + describe("pendingTasks$", () => { + it("should filter tasks to only pending tasks", async () => { + fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, [ + { + id: "completed-task-id" as SecurityTaskId, + status: SecurityTaskStatus.Completed, + }, + { + id: "pending-task-id" as SecurityTaskId, + status: SecurityTaskStatus.Pending, + }, + ] as SecurityTaskData[]); + + const { pendingTasks$ } = testBed.inject(DefaultTaskService); + + const result = await firstValueFrom(pendingTasks$("user-id" as UserId)); + + expect(result.length).toBe(1); + expect(result[0].id).toBe("pending-task-id" as SecurityTaskId); + }); + }); + + describe("refreshTasks()", () => { + it("should fetch tasks from the API", async () => { + mockApiSend.mockResolvedValue({ + data: [ + { + id: "task-id", + }, + ] as SecurityTaskResponse[], + }); + + const service = testBed.inject(DefaultTaskService); + + await service.refreshTasks("user-id" as UserId); + + expect(mockApiSend).toHaveBeenCalledWith("GET", "/tasks", null, true, true); + }); + + it("should update the local state with refreshed tasks", async () => { + mockApiSend.mockResolvedValue({ + data: [ + { + id: "task-id", + }, + ] as SecurityTaskResponse[], + }); + + const mock = fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, null); + + const service = testBed.inject(DefaultTaskService); + + await service.refreshTasks("user-id" as UserId); + + expect(mock.nextMock).toHaveBeenCalledWith([ + { + id: "task-id" as SecurityTaskId, + } as SecurityTaskData, + ]); + }); + }); + + describe("clear()", () => { + it("should clear the local state for the user", async () => { + const mock = fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, [ + { + id: "task-id" as SecurityTaskId, + } as SecurityTaskData, + ]); + + const service = testBed.inject(DefaultTaskService); + + await service.clear("user-id" as UserId); + + expect(mock.nextMock).toHaveBeenCalledWith([]); + }); + }); + + describe("markAsComplete()", () => { + it("should send an API request to mark the task as complete", async () => { + const service = testBed.inject(DefaultTaskService); + + await service.markAsComplete("task-id" as SecurityTaskId, "user-id" as UserId); + + expect(mockApiSend).toHaveBeenCalledWith( + "PATCH", + "/tasks/task-id/complete", + null, + true, + false, + ); + }); + + it("should refresh all tasks for the user after marking the task as complete", async () => { + mockApiSend + .mockResolvedValueOnce(null) // Mark as complete + .mockResolvedValueOnce({ + // Refresh tasks + data: [ + { + id: "new-task-id", + }, + ] as SecurityTaskResponse[], + }); + + const mockState = fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, [ + { + id: "old-task-id" as SecurityTaskId, + } as SecurityTaskData, + ]); + + const service = testBed.inject(DefaultTaskService); + + await service.markAsComplete("task-id" as SecurityTaskId, "user-id" as UserId); + + expect(mockApiSend).toHaveBeenCalledWith("GET", "/tasks", null, true, true); + expect(mockState.nextMock).toHaveBeenCalledWith([ + { + id: "new-task-id", + } as SecurityTaskData, + ]); + }); + }); +}); diff --git a/libs/vault/src/tasks/services/default-task.service.ts b/libs/vault/src/tasks/services/default-task.service.ts new file mode 100644 index 00000000000..2fc4ba3a937 --- /dev/null +++ b/libs/vault/src/tasks/services/default-task.service.ts @@ -0,0 +1,93 @@ +import { Injectable } from "@angular/core"; +import { map, switchMap } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid"; +import { SecurityTask, SecurityTaskStatus, TaskService } from "@bitwarden/vault"; + +import { filterOutNullish, perUserCache$ } from "../../utils/observable-utilities"; +import { SecurityTaskData } from "../models/security-task.data"; +import { SecurityTaskResponse } from "../models/security-task.response"; +import { SECURITY_TASKS } from "../state/security-task.state"; + +@Injectable() +export class DefaultTaskService implements TaskService { + constructor( + private stateProvider: StateProvider, + private apiService: ApiService, + private organizationService: OrganizationService, + ) {} + + tasksEnabled$ = perUserCache$((userId) => { + return this.organizationService + .getAll$(userId) + .pipe(map((orgs) => orgs.some((o) => o.useRiskInsights))); + }); + + tasks$ = perUserCache$((userId) => { + return this.taskState(userId).state$.pipe( + switchMap(async (tasks) => { + if (tasks == null) { + await this.fetchTasksFromApi(userId); + } + return tasks; + }), + filterOutNullish(), + map((tasks) => tasks.map((t) => new SecurityTask(t))), + ); + }); + + pendingTasks$ = perUserCache$((userId) => { + return this.tasks$(userId).pipe( + map((tasks) => tasks.filter((t) => t.status === SecurityTaskStatus.Pending)), + ); + }); + + async refreshTasks(userId: UserId): Promise { + await this.fetchTasksFromApi(userId); + } + + async clear(userId: UserId): Promise { + await this.updateTaskState(userId, []); + } + + async markAsComplete(taskId: SecurityTaskId, userId: UserId): Promise { + await this.apiService.send("PATCH", `/tasks/${taskId}/complete`, null, true, false); + await this.refreshTasks(userId); + } + + /** + * Fetches the tasks from the API and updates the local state + * @param userId + * @private + */ + private async fetchTasksFromApi(userId: UserId): Promise { + const r = await this.apiService.send("GET", "/tasks", null, true, true); + const response = new ListResponse(r, SecurityTaskResponse); + + const taskData = response.data.map((t) => new SecurityTaskData(t)); + await this.updateTaskState(userId, taskData); + } + + /** + * Returns the local state for the tasks + * @param userId + * @private + */ + private taskState(userId: UserId) { + return this.stateProvider.getUser(userId, SECURITY_TASKS); + } + + /** + * Updates the local state with the provided tasks and returns the updated state + * @param userId + * @param tasks + * @private + */ + private updateTaskState(userId: UserId, tasks: SecurityTaskData[]): Promise { + return this.taskState(userId).update(() => tasks); + } +} diff --git a/libs/vault/src/tasks/state/security-task.state.ts b/libs/vault/src/tasks/state/security-task.state.ts new file mode 100644 index 00000000000..b86a891f008 --- /dev/null +++ b/libs/vault/src/tasks/state/security-task.state.ts @@ -0,0 +1,14 @@ +import { Jsonify } from "type-fest"; + +import { SECURITY_TASKS_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; + +import { SecurityTaskData } from "../models/security-task.data"; + +export const SECURITY_TASKS = UserKeyDefinition.array( + SECURITY_TASKS_DISK, + "securityTasks", + { + deserializer: (task: Jsonify) => SecurityTaskData.fromJSON(task), + clearOn: ["logout", "lock"], + }, +); diff --git a/libs/vault/src/utils/observable-utilities.ts b/libs/vault/src/utils/observable-utilities.ts new file mode 100644 index 00000000000..bb559c600d3 --- /dev/null +++ b/libs/vault/src/utils/observable-utilities.ts @@ -0,0 +1,37 @@ +import { filter, Observable, OperatorFunction, shareReplay } from "rxjs"; + +import { UserId } from "@bitwarden/common/types/guid"; + +/** + * Builds an observable once per userId and caches it for future requests. + * The built observables are shared among subscribers with a replay buffer size of 1. + * @param create - A function that creates an observable for a given userId. + */ +export function perUserCache$( + create: (userId: UserId) => Observable, +): (userId: UserId) => Observable { + const cache = new Map>(); + return (userId: UserId) => { + let observable = cache.get(userId); + if (!observable) { + observable = create(userId).pipe(shareReplay({ bufferSize: 1, refCount: false })); + cache.set(userId, observable); + } + return observable; + }; +} + +/** + * Strongly typed observable operator that filters out null/undefined values and adjusts the return type to + * be non-nullable. + * + * @example + * ```ts + * const source$ = of(1, null, 2, undefined, 3); + * source$.pipe(filterOutNullish()).subscribe(console.log); + * // Output: 1, 2, 3 + * ``` + */ +export function filterOutNullish(): OperatorFunction { + return filter((v): v is T => v != null); +} From ba24fd54e09e7091310ded40e87b8995d4523a07 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:18:07 -0800 Subject: [PATCH 266/270] [deps]: Update github/codeql-action action to v3.28.2 (#12963) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-web.yml | 2 +- .github/workflows/scan.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 73ae0e14962..cba2429c487 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -310,7 +310,7 @@ jobs: output-format: sarif - name: Upload Grype results to GitHub - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index a09e8137b65..ac7f0ae6f71 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -46,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: cx_result.sarif From 007e2fc951f21153b9032243c1e4185e3f4de52c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:24:43 -0800 Subject: [PATCH 267/270] [deps]: Update github-action minor (#12972) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-browser.yml | 6 +-- .github/workflows/build-cli.yml | 16 +++---- .github/workflows/build-desktop.yml | 52 +++++++++++----------- .github/workflows/build-web.yml | 4 +- .github/workflows/chromatic.yml | 2 +- .github/workflows/release-browser.yml | 2 +- .github/workflows/release-cli.yml | 2 +- .github/workflows/release-desktop-beta.yml | 48 ++++++++++---------- .github/workflows/release-desktop.yml | 2 +- .github/workflows/release-web.yml | 2 +- .github/workflows/stale-bot.yml | 2 +- 11 files changed, 69 insertions(+), 69 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 974e07829c1..c9e9f588c83 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -169,7 +169,7 @@ jobs: zip -r browser-source.zip browser-source - name: Upload browser source - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: browser-source-${{ env._BUILD_NUMBER }}.zip path: browser-source.zip @@ -268,7 +268,7 @@ jobs: working-directory: browser-source/apps/browser - name: Upload extension artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ matrix.artifact_name }}-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/${{ matrix.archive_name }} @@ -405,7 +405,7 @@ jobs: ls -la - name: Upload Safari artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: dist-safari-${{ env._BUILD_NUMBER }}.zip path: apps/browser/dist/dist-safari.zip diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 02432e0a5f4..d5d78811cda 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -163,14 +163,14 @@ jobs: matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt - name: Upload unix zip asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload unix checksum asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -324,14 +324,14 @@ jobs: -t sha256 | Out-File -Encoding ASCII ./dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${env:_PACKAGE_VERSION}.txt - name: Upload windows zip asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload windows checksum asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -339,7 +339,7 @@ jobs: - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg @@ -350,7 +350,7 @@ jobs: - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip path: apps/cli/bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip @@ -421,14 +421,14 @@ jobs: run: sudo snap remove bw - name: Upload snap asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload snap checksum asset - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index b27d1486bd2..ca681dac6b0 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -232,42 +232,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -280,7 +280,7 @@ jobs: sudo npm run pack:lin:flatpak - name: Upload flatpak artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: com.bitwarden.desktop.flatpak path: apps/desktop/dist/com.bitwarden.desktop.flatpak @@ -428,91 +428,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -918,28 +918,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -1166,7 +1166,7 @@ jobs: run: npm run pack:mac:mas - name: Upload .pkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -1425,7 +1425,7 @@ jobs: zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - name: Upload masdev artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index cba2429c487..b7e8a51897c 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -164,7 +164,7 @@ jobs: run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build - name: Upload ${{ matrix.name }} artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: web-${{ env._VERSION }}-${{ matrix.name }}.zip path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip @@ -274,7 +274,7 @@ jobs: - name: Build Docker image id: build-docker - uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: apps/web file: apps/web/Dockerfile diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index a5ebd363f63..75a07431942 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -56,7 +56,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@64a9c0ca3bfb724389b0d536e544f56b7b5ff5b3 # v11.20.2 + uses: chromaui/action@8a12962215a66cd05b1ac5b0f1c08768d1aab155 # v11.25.0 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 7e8722dc79f..3e54e79a303 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -128,7 +128,7 @@ jobs: - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 with: artifacts: 'browser-source-${{ needs.setup.outputs.release_version }}.zip, dist-chrome-${{ needs.setup.outputs.release_version }}.zip, diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index d16cd744d7d..a1bc36cf85e 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -73,7 +73,7 @@ jobs: - name: Create release if: ${{ inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 env: PKG_VERSION: ${{ needs.setup.outputs.release_version }} with: diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 08174dc552e..ea0feb10e3d 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -158,42 +158,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -299,91 +299,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -707,28 +707,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -915,7 +915,7 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - name: Upload .pkg artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index ba934235b44..3285ad468d6 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -96,7 +96,7 @@ jobs: file_path: "apps/desktop/artifacts/sha256-checksums.txt" - name: Create Release - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 if: ${{ steps.release_channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} env: PKG_VERSION: ${{ steps.version.outputs.version }} diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index faa398f6d67..0301b814796 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -81,7 +81,7 @@ jobs: - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 with: name: "Web v${{ needs.setup.outputs.release_version }}" commit: ${{ github.sha }} diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 6caa7b99331..abb292f53f3 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: 'Run stale action' - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: stale-issue-label: 'needs-reply' stale-pr-label: 'needs-changes' From cc7defc4475bfe237d1fb13b4135a62288def55f Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:50:05 -0500 Subject: [PATCH 268/270] [PM-13938] Changes to disabled user from seeing password that they shouldn't see or edit easily. (#12161) * Changes to disabled user from seeing password that they shouldn't see or edit easily. * Fixing defects on PM-13938 * undoing unecessary change * Updating tests --------- Co-authored-by: --global <> Co-authored-by: kejaeger <138028972+kejaeger@users.noreply.github.com> --- .../custom-fields/custom-fields.component.html | 2 +- .../custom-fields.component.spec.ts | 17 +++++++++++++++-- .../custom-fields-v2.component.html | 2 ++ 3 files changed, 18 insertions(+), 3 deletions(-) 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 63941c6a467..fab3c8f1ab1 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 @@ -46,7 +46,7 @@ bitSuffix bitPasswordInputToggle data-testid="visibility-for-custom-hidden-field" - [disabled]="!canViewPasswords(i)" + *ngIf="canViewPasswords(i)" (toggledChange)="logHiddenEvent($event)" > diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts index a5e57d2858f..b523d9ef933 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts @@ -113,7 +113,7 @@ describe("CustomFieldsComponent", () => { ]); }); - it("forbids a user to view hidden fields when the cipher `viewPassword` is false", () => { + it("when `viewPassword` is false the user cannot see the view toggle option", () => { originalCipherView.viewPassword = false; originalCipherView.fields = mockFieldViews; @@ -123,7 +123,20 @@ describe("CustomFieldsComponent", () => { const button = fixture.debugElement.query(By.directive(BitPasswordInputToggleDirective)); - expect(button.nativeElement.disabled).toBe(true); + expect(button).toBeFalsy(); + }); + + it("when `viewPassword` is true the user can see the view toggle option", () => { + originalCipherView.viewPassword = true; + originalCipherView.fields = mockFieldViews; + + component.ngOnInit(); + + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.directive(BitPasswordInputToggleDirective)); + + expect(button).toBeTruthy(); }); describe("linkedFieldOptions", () => { diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index ab31ede57bb..8eee79729af 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -36,6 +36,7 @@