From 0c0c2039edd4ee442456b6c5a4863a92b9a50e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Gonc=CC=A7alves?= Date: Tue, 2 Apr 2024 17:18:45 +0100 Subject: [PATCH] Conflict resolution --- .checkmarx/config.yml | 1 + .eslintrc.json | 11 +- .github/CODEOWNERS | 1 + .github/renovate.json | 4 + .github/workflows/build-desktop.yml | 20 +- .github/workflows/build-web.yml | 4 +- .github/workflows/deploy-web.yml | 34 + .github/workflows/release-desktop-beta.yml | 15 +- .github/workflows/release-web.yml | 93 -- .github/workflows/scan.yml | 9 +- .github/workflows/version-bump.yml | 28 +- apps/browser/.eslintrc.json | 26 + apps/browser/package.json | 2 +- apps/browser/src/_locales/ar/messages.json | 6 - apps/browser/src/_locales/az/messages.json | 6 - apps/browser/src/_locales/be/messages.json | 6 - apps/browser/src/_locales/bg/messages.json | 6 - apps/browser/src/_locales/bn/messages.json | 6 - apps/browser/src/_locales/bs/messages.json | 6 - apps/browser/src/_locales/ca/messages.json | 6 - apps/browser/src/_locales/cs/messages.json | 6 - apps/browser/src/_locales/cy/messages.json | 6 - apps/browser/src/_locales/da/messages.json | 24 +- apps/browser/src/_locales/de/messages.json | 16 +- apps/browser/src/_locales/el/messages.json | 6 - apps/browser/src/_locales/en/messages.json | 12 +- apps/browser/src/_locales/en_GB/messages.json | 6 - apps/browser/src/_locales/en_IN/messages.json | 6 - apps/browser/src/_locales/es/messages.json | 6 - apps/browser/src/_locales/et/messages.json | 6 - apps/browser/src/_locales/eu/messages.json | 6 - apps/browser/src/_locales/fa/messages.json | 6 - apps/browser/src/_locales/fi/messages.json | 6 - apps/browser/src/_locales/fil/messages.json | 6 - apps/browser/src/_locales/fr/messages.json | 104 +- apps/browser/src/_locales/gl/messages.json | 6 - apps/browser/src/_locales/he/messages.json | 6 - apps/browser/src/_locales/hi/messages.json | 6 - apps/browser/src/_locales/hr/messages.json | 6 - apps/browser/src/_locales/hu/messages.json | 6 - apps/browser/src/_locales/id/messages.json | 8 +- apps/browser/src/_locales/it/messages.json | 6 - apps/browser/src/_locales/ja/messages.json | 6 - apps/browser/src/_locales/ka/messages.json | 6 - apps/browser/src/_locales/km/messages.json | 6 - apps/browser/src/_locales/kn/messages.json | 6 - apps/browser/src/_locales/ko/messages.json | 6 - apps/browser/src/_locales/lt/messages.json | 6 - apps/browser/src/_locales/lv/messages.json | 6 - apps/browser/src/_locales/ml/messages.json | 6 - apps/browser/src/_locales/mr/messages.json | 6 - apps/browser/src/_locales/my/messages.json | 6 - apps/browser/src/_locales/nb/messages.json | 6 - apps/browser/src/_locales/ne/messages.json | 6 - apps/browser/src/_locales/nl/messages.json | 6 - apps/browser/src/_locales/nn/messages.json | 6 - apps/browser/src/_locales/or/messages.json | 6 - apps/browser/src/_locales/pl/messages.json | 6 - apps/browser/src/_locales/pt_BR/messages.json | 6 - apps/browser/src/_locales/pt_PT/messages.json | 8 +- apps/browser/src/_locales/ro/messages.json | 6 - apps/browser/src/_locales/ru/messages.json | 6 - apps/browser/src/_locales/si/messages.json | 6 - apps/browser/src/_locales/sk/messages.json | 6 - apps/browser/src/_locales/sl/messages.json | 6 - apps/browser/src/_locales/sr/messages.json | 6 - apps/browser/src/_locales/sv/messages.json | 102 +- apps/browser/src/_locales/te/messages.json | 6 - apps/browser/src/_locales/th/messages.json | 6 - apps/browser/src/_locales/tr/messages.json | 6 - apps/browser/src/_locales/uk/messages.json | 6 - apps/browser/src/_locales/vi/messages.json | 6 - apps/browser/src/_locales/zh_CN/messages.json | 22 +- apps/browser/src/_locales/zh_TW/messages.json | 56 +- .../service-factories/auth-service.factory.ts | 11 +- .../device-trust-crypto-service.factory.ts | 25 +- .../key-connector-service.factory.ts | 12 +- .../login-email-service.factory.ts | 28 + .../login-strategy-service.factory.ts | 14 +- .../token-service.factory.ts | 20 +- ...user-decryption-options-service.factory.ts | 46 + .../user-verification-service.factory.ts | 6 + .../services/account-switcher.service.ts | 2 +- apps/browser/src/auth/popup/hint.component.ts | 6 +- .../src/auth/popup/home.component.html | 2 +- apps/browser/src/auth/popup/home.component.ts | 51 +- apps/browser/src/auth/popup/lock.component.ts | 3 + .../popup/login-via-auth-request.component.ts | 16 +- .../src/auth/popup/login.component.html | 2 +- .../browser/src/auth/popup/login.component.ts | 20 +- .../popup/services/unauth-guard.service.ts | 3 - .../src/auth/popup/set-password.component.ts | 3 + apps/browser/src/auth/popup/sso.component.ts | 18 +- .../src/auth/popup/two-factor.component.ts | 24 +- .../abstractions/notification.background.ts | 2 +- .../notification.background.spec.ts | 18 +- .../background/notification.background.ts | 6 +- .../background/overlay.background.spec.ts | 99 +- .../autofill/background/overlay.background.ts | 30 +- .../background/web-request.background.ts | 2 +- .../browser/context-menu-clicked-handler.ts | 5 +- .../autofill/content/autofill-init.spec.ts | 1 + .../src/autofill/content/autofiller.ts | 2 +- apps/browser/src/autofill/notification/bar.ts | 6 +- .../autofill-overlay-iframe.service.ts | 2 +- .../pages/list/autofill-overlay-list.ts | 2 +- .../services/abstractions/autofill.service.ts | 2 +- .../autofill-overlay-content.service.spec.ts | 53 +- .../autofill-overlay-content.service.ts | 6 +- .../src/autofill/services/autofill.service.ts | 2 +- .../collect-autofill-content.service.spec.ts | 73 +- .../collect-autofill-content.service.ts | 82 +- .../dom-element-visibility.service.ts | 2 +- .../insert-autofill-content.service.spec.ts | 28 +- .../insert-autofill-content.service.ts | 16 +- .../browser/src/background/idle.background.ts | 6 +- .../browser/src/background/main.background.ts | 80 +- .../src/background/runtime.background.ts | 19 +- .../service-factories/send-service.factory.ts | 4 +- .../vault-timeout-settings-service.factory.ts | 6 + apps/browser/src/manifest.json | 22 +- apps/browser/src/manifest.v3.json | 35 +- apps/browser/src/platform/background.ts | 65 +- ...g-account-profile-state-service.factory.ts | 7 +- .../config-api.service.factory.ts | 10 +- .../config-service.factory.ts | 32 +- .../storage-service.factory.ts | 24 +- .../src/platform/browser/browser-api.ts | 6 +- .../offscreen-document/offscreen-document.ts | 4 +- .../services/browser-file-download.service.ts | 4 +- .../abstractions/browser-state.service.ts | 13 - .../services/browser-config.service.ts | 28 - .../services/browser-environment.service.ts | 27 +- ...ssaging-private-mode-background.service.ts | 2 +- ...er-messaging-private-mode-popup.service.ts | 2 +- .../services/browser-state.service.spec.ts | 22 - .../services/browser-state.service.ts | 45 - .../src/platform/services/i18n.service.ts | 7 + ...cal-backed-session-storage.service.spec.ts | 104 +- .../local-backed-session-storage.service.ts | 94 +- apps/browser/src/popup/app.component.ts | 8 +- apps/browser/src/popup/app.module.ts | 2 + apps/browser/src/popup/scss/base.scss | 4 +- .../src/popup/services/init.service.ts | 3 - .../popup/services/popup-search.service.ts | 6 +- .../src/popup/services/services.module.ts | 741 +++++------ .../src/popup/settings/about.component.html | 30 +- .../src/popup/settings/about.component.ts | 15 +- .../src/popup/settings/settings.component.ts | 12 +- .../src/services/browser-send.service.ts | 15 - .../fileless-importer.background.spec.ts | 2 +- .../fileless-importer.background.ts | 4 +- .../popup/settings/export.component.html | 2 +- .../tools/popup/settings/export.component.ts | 2 +- .../src/vault/fido2/content/content-script.ts | 7 +- .../components/vault/add-edit.component.html | 18 +- .../components/vault/add-edit.component.ts | 4 +- .../components/vault/collections.component.ts | 11 +- .../components/vault/current-tab.component.ts | 23 +- .../vault/vault-filter.component.ts | 12 +- .../components/vault/vault-items.component.ts | 4 +- .../vault-browser-state.service.spec.ts | 87 ++ .../services/vault-browser-state.service.ts | 65 + apps/browser/store/locales/gl/copy.resx | 48 +- apps/browser/tsconfig.json | 1 + apps/cli/package.json | 4 +- apps/cli/src/auth/commands/login.command.ts | 4 +- apps/cli/src/bw.ts | 55 +- apps/cli/src/commands/config.command.ts | 13 +- .../convert-to-key-connector.command.ts | 11 +- apps/cli/src/commands/status.command.ts | 9 +- .../platform/services/cli-config.service.ts | 9 - .../src/tools/send/commands/create.command.ts | 3 +- .../src/tools/send/commands/get.command.ts | 4 +- .../src/tools/send/commands/list.command.ts | 5 +- .../tools/send/commands/receive.command.ts | 8 +- .../send/commands/remove-password.command.ts | 5 +- apps/desktop/desktop_native/Cargo.lock | 95 +- apps/desktop/desktop_native/Cargo.toml | 10 +- apps/desktop/electron-builder.json | 66 +- apps/desktop/package.json | 2 +- .../src/app/accounts/settings.component.html | 21 +- .../src/app/accounts/settings.component.ts | 73 +- apps/desktop/src/app/app.component.ts | 7 +- apps/desktop/src/app/app.module.ts | 5 +- .../app/layout/account-switcher.component.ts | 11 +- apps/desktop/src/app/services/init.service.ts | 22 - .../src/app/services/services.module.ts | 317 +++-- .../app/tools/export/export.component.html | 6 +- .../src/app/tools/export/export.component.ts | 2 +- .../src/app/tools/generator.component.html | 4 +- .../app/tools/send/add-edit.component.html | 8 +- apps/desktop/src/auth/hint.component.ts | 6 +- apps/desktop/src/auth/lock.component.spec.ts | 11 + apps/desktop/src/auth/lock.component.ts | 3 + .../login/login-via-auth-request.component.ts | 20 +- .../src/auth/login/login.component.html | 4 +- .../desktop/src/auth/login/login.component.ts | 17 +- .../src/auth/set-password.component.ts | 3 + apps/desktop/src/auth/sso.component.ts | 11 +- apps/desktop/src/auth/two-factor.component.ts | 23 +- .../desktop-autofill-settings.service.ts | 29 + apps/desktop/src/locales/af/messages.json | 21 +- apps/desktop/src/locales/ar/messages.json | 21 +- apps/desktop/src/locales/az/messages.json | 21 +- apps/desktop/src/locales/be/messages.json | 21 +- apps/desktop/src/locales/bg/messages.json | 21 +- apps/desktop/src/locales/bn/messages.json | 21 +- apps/desktop/src/locales/bs/messages.json | 21 +- apps/desktop/src/locales/ca/messages.json | 21 +- apps/desktop/src/locales/cs/messages.json | 21 +- apps/desktop/src/locales/cy/messages.json | 21 +- apps/desktop/src/locales/da/messages.json | 31 +- apps/desktop/src/locales/de/messages.json | 21 +- apps/desktop/src/locales/el/messages.json | 229 ++-- apps/desktop/src/locales/en/messages.json | 27 +- apps/desktop/src/locales/en_GB/messages.json | 21 +- apps/desktop/src/locales/en_IN/messages.json | 21 +- apps/desktop/src/locales/eo/messages.json | 21 +- apps/desktop/src/locales/es/messages.json | 21 +- apps/desktop/src/locales/et/messages.json | 21 +- apps/desktop/src/locales/eu/messages.json | 21 +- apps/desktop/src/locales/fa/messages.json | 21 +- apps/desktop/src/locales/fi/messages.json | 21 +- apps/desktop/src/locales/fil/messages.json | 21 +- apps/desktop/src/locales/fr/messages.json | 23 +- apps/desktop/src/locales/gl/messages.json | 21 +- apps/desktop/src/locales/he/messages.json | 29 +- apps/desktop/src/locales/hi/messages.json | 21 +- apps/desktop/src/locales/hr/messages.json | 21 +- apps/desktop/src/locales/hu/messages.json | 21 +- apps/desktop/src/locales/id/messages.json | 21 +- apps/desktop/src/locales/it/messages.json | 21 +- apps/desktop/src/locales/ja/messages.json | 21 +- apps/desktop/src/locales/ka/messages.json | 21 +- apps/desktop/src/locales/km/messages.json | 21 +- apps/desktop/src/locales/kn/messages.json | 21 +- apps/desktop/src/locales/ko/messages.json | 21 +- apps/desktop/src/locales/lt/messages.json | 21 +- apps/desktop/src/locales/lv/messages.json | 25 +- apps/desktop/src/locales/me/messages.json | 21 +- apps/desktop/src/locales/ml/messages.json | 21 +- apps/desktop/src/locales/mr/messages.json | 21 +- apps/desktop/src/locales/my/messages.json | 21 +- apps/desktop/src/locales/nb/messages.json | 21 +- apps/desktop/src/locales/ne/messages.json | 21 +- apps/desktop/src/locales/nl/messages.json | 21 +- apps/desktop/src/locales/nn/messages.json | 21 +- apps/desktop/src/locales/or/messages.json | 21 +- apps/desktop/src/locales/pl/messages.json | 21 +- apps/desktop/src/locales/pt_BR/messages.json | 21 +- apps/desktop/src/locales/pt_PT/messages.json | 21 +- apps/desktop/src/locales/ro/messages.json | 21 +- apps/desktop/src/locales/ru/messages.json | 21 +- apps/desktop/src/locales/si/messages.json | 21 +- apps/desktop/src/locales/sk/messages.json | 21 +- apps/desktop/src/locales/sl/messages.json | 21 +- apps/desktop/src/locales/sr/messages.json | 21 +- apps/desktop/src/locales/sv/messages.json | 139 +- apps/desktop/src/locales/te/messages.json | 21 +- apps/desktop/src/locales/th/messages.json | 21 +- apps/desktop/src/locales/tr/messages.json | 21 +- apps/desktop/src/locales/uk/messages.json | 21 +- apps/desktop/src/locales/vi/messages.json | 21 +- apps/desktop/src/locales/zh_CN/messages.json | 23 +- apps/desktop/src/locales/zh_TW/messages.json | 21 +- apps/desktop/src/main.ts | 90 +- apps/desktop/src/main/menu/menu.help.ts | 107 +- apps/desktop/src/main/menu/menu.main.ts | 8 +- apps/desktop/src/main/menu/menubar.ts | 5 + apps/desktop/src/main/messaging.main.ts | 16 +- .../desktop/src/main/native-messaging.main.ts | 2 +- apps/desktop/src/main/tray.main.ts | 18 +- apps/desktop/src/main/window.main.ts | 19 +- apps/desktop/src/package-lock.json | 4 +- apps/desktop/src/package.json | 2 +- .../biometrics.service.abstraction.ts | 32 +- .../platform}/models/domain/window-state.ts | 2 +- .../services/desktop-settings.service.ts | 180 +++ .../services/electron-state.service.ts | 35 - .../platform/services/i18n.main.service.ts | 8 + .../services/i18n.renderer.service.ts | 8 + .../illegal-secure-storage.service.ts | 28 + .../native-message-handler.service.ts | 8 +- .../vault/app/vault/add-edit.component.html | 21 +- .../src/vault/app/vault/add-edit.component.ts | 4 +- .../vault/app/vault/collections.component.ts | 11 +- apps/desktop/tsconfig.json | 1 + apps/desktop/webpack.renderer.js | 1 + apps/web/config/development.json | 9 + apps/web/config/euqa.json | 16 + apps/web/config/qa.json | 16 + apps/web/jest.config.js | 10 +- apps/web/package.json | 2 +- .../core/services/group/group.service.ts | 6 +- .../core/services/user-admin.service.ts | 4 +- .../layouts/organization-layout.component.ts | 2 +- .../member-dialog/member-dialog.component.ts | 4 +- .../settings/account.component.ts | 4 +- .../vault-export/org-vault-export.module.ts | 9 +- apps/web/src/app/app.component.ts | 7 +- apps/web/src/app/auth/hint.component.ts | 6 +- .../user-key-rotation.service.spec.ts | 13 +- .../key-rotation/user-key-rotation.service.ts | 13 +- apps/web/src/app/auth/lock.component.ts | 3 + .../login/login-via-auth-request.component.ts | 70 +- .../src/app/auth/login/login.component.html | 4 +- .../web/src/app/auth/login/login.component.ts | 23 +- .../emergency-add-edit-cipher.component.ts | 4 +- apps/web/src/app/auth/sso.component.ts | 11 +- apps/web/src/app/auth/two-factor.component.ts | 21 +- .../billing/individual/premium.component.ts | 2 +- .../user-subscription.component.html | 2 +- .../individual/user-subscription.component.ts | 82 +- ...nization-subscription-cloud.component.html | 31 +- ...ganization-subscription-cloud.component.ts | 42 +- ...ization-subscription-selfhost.component.ts | 8 +- .../billing/shared/add-credit.component.ts | 10 +- .../environment-selector.component.html | 23 +- .../environment-selector.component.ts | 21 +- apps/web/src/app/core/core.module.ts | 21 +- apps/web/src/app/core/init.service.ts | 16 - .../src/app/layouts/user-layout.component.ts | 2 +- .../app/platform/web-environment.service.ts | 62 + .../src/app/platform/web-migration-runner.ts | 2 +- .../tools/vault-export/export.component.html | 4 +- .../tools/vault-export/export.component.ts | 3 +- .../app/tools/vault-export/export.module.ts | 4 +- .../collection-dialog.component.ts | 4 +- .../vault-items/vault-item-event.ts | 3 +- .../vault-items/vault-items.component.html | 9 + .../vault-items/vault-items.component.ts | 16 +- .../vault-items/vault-items.stories.ts | 4 +- .../individual-vault/add-edit.component.html | 15 +- .../individual-vault/add-edit.component.ts | 4 +- .../collections.component.html | 1 + .../individual-vault/collections.component.ts | 14 +- .../organization-options.component.ts | 6 +- .../abstractions/vault-filter.service.ts | 4 +- .../services/vault-filter.service.spec.ts | 27 +- .../services/vault-filter.service.ts | 27 +- .../vault-onboarding.component.spec.ts | 8 +- .../vault-onboarding.component.ts | 6 +- .../individual-vault/vault.component.html | 1 - .../vault/individual-vault/vault.component.ts | 15 +- .../app/vault/org-vault/add-edit.component.ts | 14 +- ...ollection-assignment-dialog.component.html | 66 + ...-collection-assignment-dialog.component.ts | 191 +++ .../index.ts | 1 + .../vault/org-vault/collections.component.ts | 22 +- .../vault-filter/vault-filter.service.ts | 5 +- .../app/vault/org-vault/vault.component.html | 5 +- .../app/vault/org-vault/vault.component.ts | 87 +- apps/web/src/locales/af/messages.json | 43 +- apps/web/src/locales/ar/messages.json | 43 +- apps/web/src/locales/az/messages.json | 47 +- apps/web/src/locales/be/messages.json | 43 +- apps/web/src/locales/bg/messages.json | 47 +- apps/web/src/locales/bn/messages.json | 43 +- apps/web/src/locales/bs/messages.json | 43 +- apps/web/src/locales/ca/messages.json | 51 +- apps/web/src/locales/cs/messages.json | 47 +- apps/web/src/locales/cy/messages.json | 43 +- apps/web/src/locales/da/messages.json | 47 +- apps/web/src/locales/de/messages.json | 47 +- apps/web/src/locales/el/messages.json | 43 +- apps/web/src/locales/en/messages.json | 43 +- apps/web/src/locales/en_GB/messages.json | 43 +- apps/web/src/locales/en_IN/messages.json | 43 +- apps/web/src/locales/eo/messages.json | 43 +- apps/web/src/locales/es/messages.json | 43 +- apps/web/src/locales/et/messages.json | 43 +- apps/web/src/locales/eu/messages.json | 43 +- apps/web/src/locales/fa/messages.json | 43 +- apps/web/src/locales/fi/messages.json | 47 +- apps/web/src/locales/fil/messages.json | 43 +- apps/web/src/locales/fr/messages.json | 121 +- apps/web/src/locales/gl/messages.json | 43 +- apps/web/src/locales/he/messages.json | 43 +- apps/web/src/locales/hi/messages.json | 43 +- apps/web/src/locales/hr/messages.json | 43 +- apps/web/src/locales/hu/messages.json | 47 +- apps/web/src/locales/id/messages.json | 43 +- apps/web/src/locales/it/messages.json | 47 +- apps/web/src/locales/ja/messages.json | 43 +- apps/web/src/locales/ka/messages.json | 43 +- apps/web/src/locales/km/messages.json | 43 +- apps/web/src/locales/kn/messages.json | 43 +- apps/web/src/locales/ko/messages.json | 43 +- apps/web/src/locales/lv/messages.json | 47 +- apps/web/src/locales/ml/messages.json | 43 +- apps/web/src/locales/mr/messages.json | 43 +- apps/web/src/locales/my/messages.json | 43 +- apps/web/src/locales/nb/messages.json | 43 +- apps/web/src/locales/ne/messages.json | 43 +- apps/web/src/locales/nl/messages.json | 107 +- apps/web/src/locales/nn/messages.json | 43 +- apps/web/src/locales/or/messages.json | 43 +- apps/web/src/locales/pl/messages.json | 43 +- apps/web/src/locales/pt_BR/messages.json | 47 +- apps/web/src/locales/pt_PT/messages.json | 43 +- apps/web/src/locales/ro/messages.json | 43 +- apps/web/src/locales/ru/messages.json | 47 +- apps/web/src/locales/si/messages.json | 43 +- apps/web/src/locales/sk/messages.json | 47 +- apps/web/src/locales/sl/messages.json | 43 +- apps/web/src/locales/sr/messages.json | 49 +- apps/web/src/locales/sr_CS/messages.json | 43 +- apps/web/src/locales/sv/messages.json | 47 +- apps/web/src/locales/te/messages.json | 43 +- apps/web/src/locales/th/messages.json | 43 +- apps/web/src/locales/tr/messages.json | 47 +- apps/web/src/locales/uk/messages.json | 47 +- apps/web/src/locales/vi/messages.json | 43 +- apps/web/src/locales/zh_CN/messages.json | 55 +- apps/web/src/locales/zh_TW/messages.json | 43 +- apps/web/src/translation-constants.ts | 9 + apps/web/tsconfig.json | 1 + apps/web/webpack.config.js | 20 +- .../organizations/manage/scim.component.ts | 14 +- .../providers/providers-layout.component.ts | 2 +- .../providers/setup/setup.component.ts | 2 +- .../bit-web/src/app/app-routing.module.ts | 1 + .../src/app/auth/sso/sso.component.html | 2 +- .../bit-web/src/app/auth/sso/sso.component.ts | 4 +- .../layout/navigation.component.ts | 6 +- bitwarden_license/bit-web/tsconfig.json | 1 + .../components/collections.component.ts | 17 +- ...base-login-decryption-options.component.ts | 62 +- .../components/captcha-protected.component.ts | 4 +- .../environment-selector.component.html | 69 +- .../environment-selector.component.ts | 53 +- .../auth/components/environment.component.ts | 39 +- .../src/auth/components/hint.component.ts | 6 +- .../src/auth/components/lock.component.ts | 11 +- .../login-via-auth-request.component.ts | 45 +- .../src/auth/components/login.component.ts | 56 +- .../auth/components/set-password.component.ts | 15 +- .../src/auth/components/sso.component.spec.ts | 116 +- .../src/auth/components/sso.component.ts | 42 +- .../two-factor-options.component.ts | 6 +- .../components/two-factor.component.spec.ts | 113 +- .../auth/components/two-factor.component.ts | 42 +- libs/angular/src/auth/guards/lock.guard.ts | 2 +- .../angular/src/auth/guards/redirect.guard.ts | 2 +- .../guards/tde-decryption-required.guard.ts | 2 +- .../angular/src/components/share.component.ts | 3 +- .../directives/if-feature.directive.spec.ts | 8 +- .../src/directives/if-feature.directive.ts | 4 +- libs/angular/src/jslib.module.ts | 3 - .../form-validation-errors.service.ts | 2 +- .../platform/guard/feature-flag.guard.spec.ts | 8 +- .../src/platform/guard/feature-flag.guard.ts | 4 +- .../services/logging-error-handler.ts | 22 + .../theming/theming.service.abstraction.ts | 4 +- .../src/platform/utils/safe-provider.ts | 82 +- .../src/services/jslib-services.module.ts | 102 +- .../src/tools/send/add-edit.component.ts | 6 +- libs/angular/src/tools/send/send.component.ts | 8 +- .../vault/components/add-edit.component.ts | 20 +- .../src/vault/components/icon.component.ts | 7 +- .../src/vault/components/premium.component.ts | 14 +- .../user-verification-dialog.component.html | 4 +- .../user-verification-dialog.component.ts | 2 + .../user-verification-dialog.types.ts | 4 +- ...ser-verification-form-input.component.html | 4 +- .../user-verification-form-input.component.ts | 2 + .../auth-request.service.abstraction.ts | 12 + libs/auth/src/common/abstractions/index.ts | 2 + .../abstractions/login-email.service.ts | 38 + .../abstractions/login-strategy.service.ts | 9 - ...-decryption-options.service.abstraction.ts | 34 + .../auth-request-login.strategy.spec.ts | 4 + .../auth-request-login.strategy.ts | 8 +- .../login-strategies/login.strategy.spec.ts | 43 +- .../common/login-strategies/login.strategy.ts | 23 +- .../password-login.strategy.spec.ts | 4 + .../password-login.strategy.ts | 45 +- .../sso-login.strategy.spec.ts | 8 +- .../login-strategies/sso-login.strategy.ts | 16 +- .../user-api-login.strategy.spec.ts | 20 +- .../user-api-login.strategy.ts | 8 +- .../webauthn-login.strategy.spec.ts | 4 + .../webauthn-login.strategy.ts | 3 + libs/auth/src/common/models/domain/index.ts | 1 + .../models/domain/user-decryption-options.ts | 165 +++ libs/auth/src/common/models/index.ts | 1 + .../spec/fake-user-decryption-options.ts | 38 + libs/auth/src/common/models/spec/index.ts | 1 + .../auth-request/auth-request.service.spec.ts | 17 + .../auth-request/auth-request.service.ts | 16 +- libs/auth/src/common/services/index.ts | 2 + .../login-email/login-email.service.ts | 52 + .../login-strategy.service.spec.ts | 9 +- .../login-strategy.service.ts | 25 +- .../user-decryption-options.service.spec.ts | 94 ++ .../user-decryption-options.service.ts | 47 + libs/common/spec/fake-account-service.ts | 3 + .../spec/matchers/to-almost-equal.spec.ts | 54 + libs/common/spec/matchers/to-almost-equal.ts | 20 + libs/common/spec/observable-tracker.ts | 86 ++ libs/common/src/abstractions/api.service.ts | 1 - .../organization-api.service.abstraction.ts | 1 - .../organization/organization-api.service.ts | 4 - .../abstractions/anonymous-hub.service.ts | 4 +- .../src/auth/abstractions/auth.service.ts | 16 +- .../src/auth/abstractions/avatar.service.ts | 11 + ...device-trust-crypto.service.abstraction.ts | 27 +- .../abstractions/key-connector.service.ts | 1 - .../src/auth/abstractions/login.service.ts | 8 - .../src/auth/abstractions/token.service.ts | 14 +- .../src/auth/models/domain/auth-result.ts | 2 +- .../auth/models/domain/environment-urls.ts | 16 - .../key-connector-user-decryption-option.ts | 3 - .../trusted-device-user-decryption-option.ts | 7 - .../auth/services/anonymous-hub.service.ts | 31 +- .../src/auth/services/auth.service.spec.ts | 161 +++ libs/common/src/auth/services/auth.service.ts | 51 +- .../src/auth/services/avatar.service.ts | 4 + ...ice-trust-crypto.service.implementation.ts | 152 ++- .../device-trust-crypto.service.spec.ts | 339 +++-- .../services/key-connector.service.spec.ts | 376 ++++++ .../auth/services/key-connector.service.ts | 60 +- .../common/src/auth/services/login.service.ts | 35 - .../src/auth/services/token.service.spec.ts | 371 +++--- .../common/src/auth/services/token.service.ts | 212 ++- .../src/auth/services/token.state.spec.ts | 2 - libs/common/src/auth/services/token.state.ts | 12 +- .../user-verification.service.ts | 21 +- .../webauthn-login-api.service.ts | 5 +- ...ling-account-profile-state.service.spec.ts | 189 ++- .../billing-account-profile-state.service.ts | 28 +- .../billing/services/billing-api.service.ts | 4 +- libs/common/src/enums/feature-flag.enum.ts | 2 - .../platform/abstractions/app-id.service.ts | 8 +- .../abstractions/broadcaster.service.ts | 6 +- .../config/config-api.service.abstraction.ts | 6 +- .../config/config.service.abstraction.ts | 30 - .../abstractions/config/config.service.ts | 47 + .../abstractions/config/server-config.ts | 5 - .../abstractions/crypto-function.service.ts | 62 +- .../platform/abstractions/crypto.service.ts | 169 +-- .../platform/abstractions/encrypt.service.ts | 23 +- .../abstractions/environment.service.ts | 135 +- .../file-download/file-download.service.ts | 2 +- .../file-upload/file-upload.service.ts | 4 +- .../src/platform/abstractions/i18n.service.ts | 4 +- .../abstractions/key-generation.service.ts | 14 +- .../src/platform/abstractions/log.service.ts | 10 +- .../abstractions/messaging.service.ts | 2 +- .../abstractions/platform-utils.service.ts | 52 +- .../platform/abstractions/state.service.ts | 62 +- .../platform/abstractions/system.service.ts | 8 +- .../abstractions/translation.service.ts | 12 +- .../abstractions/validation.service.ts | 2 +- .../biometric-state.service.spec.ts | 87 +- .../biometrics/biometric-state.service.ts | 80 +- .../biometrics/biometric.state.spec.ts | 2 +- .../platform/biometrics/biometric.state.ts | 3 +- .../models/domain/account-keys.spec.ts | 38 - .../src/platform/models/domain/account.ts | 118 -- .../platform/models/domain/global-state.ts | 20 - .../services/config/config-api.service.ts | 12 +- .../services/config/config.service.spec.ts | 357 +++-- .../services/config/config.service.ts | 127 -- .../services/config/default-config.service.ts | 177 +++ .../src/platform/services/crypto.service.ts | 16 +- .../default-environment.service.spec.ts | 418 ++++++ .../services/default-environment.service.ts | 433 +++++++ .../services/environment.service.spec.ts | 535 -------- .../platform/services/environment.service.ts | 418 ------ .../migration-builder.service.spec.ts | 1 + .../src/platform/services/migration-runner.ts | 1 + .../src/platform/services/state.service.ts | 437 +------ .../platform/services/translation.service.ts | 7 + .../platform/state/derived-state.provider.ts | 4 +- .../platform/state/global-state.provider.ts | 2 +- libs/common/src/platform/state/index.ts | 2 +- .../src/platform/state/state-definitions.ts | 19 +- .../src/platform/state/state.provider.ts | 8 +- .../src/platform/state/user-state.provider.ts | 2 +- libs/common/src/platform/state/user-state.ts | 21 +- .../platform/theming/theme-state.service.ts | 4 +- libs/common/src/services/api.service.ts | 45 +- .../src/services/notifications.service.ts | 5 +- .../vault-timeout-settings.service.spec.ts | 39 +- .../vault-timeout-settings.service.ts | 18 +- libs/common/src/state-migrations/migrate.ts | 20 +- .../migration-builder.spec.ts | 14 +- .../state-migrations/migration-helper.spec.ts | 18 +- .../src/state-migrations/migration-helper.ts | 19 +- ...ard-and-identity-to-state-provider.spec.ts | 4 +- ...how-card-and-identity-to-state-provider.ts | 4 +- ...igrate-token-svc-to-state-provider.spec.ts | 144 ++- .../38-migrate-token-svc-to-state-provider.ts | 20 +- ...cryption-options-to-state-provider.spec.ts | 238 ++++ ...er-decryption-options-to-state-provider.ts | 57 + .../45-merge-environment-state.spec.ts | 164 +++ .../migrations/45-merge-environment-state.ts | 83 ++ ...ete-orphaned-biometric-prompt-data.spec.ts | 28 + ...6-delete-orphaned-biometric-prompt-data.ts | 23 + .../47-move-desktop-settings.spec.ts | 116 ++ .../migrations/47-move-desktop-settings.ts | 128 ++ .../48-move-ddg-to-state-provider.spec.ts | 47 + .../48-move-ddg-to-state-provider.ts | 40 + .../49-move-account-server-configs.spec.ts | 112 ++ .../49-move-account-server-configs.ts | 51 + ...ve-key-connector-to-state-provider.spec.ts | 174 +++ ...50-move-key-connector-to-state-provider.ts | 78 ++ ...emembered-email-to-state-providers.spec.ts | 81 ++ ...ove-remembered-email-to-state-providers.ts | 46 + .../52-delete-installed-version.spec.ts | 35 + .../migrations/52-delete-installed-version.ts | 19 + ...rust-crypto-svc-to-state-providers.spec.ts | 171 +++ ...ice-trust-crypto-svc-to-state-providers.ts | 95 ++ ...move-local-data-to-state-provider.spec.ts} | 10 +- ...> 54-move-local-data-to-state-provider.ts} | 2 +- .../src/state-migrations/migrator.spec.ts | 2 +- .../generator-history.abstraction.ts | 47 + .../generator-strategy.abstraction.ts | 9 +- .../default-generator.service.spec.ts | 36 +- .../generator/default-generator.service.ts | 14 +- .../history/generated-credential.spec.ts | 58 + .../generator/history/generated-credential.ts | 47 + .../src/tools/generator/history/index.ts | 2 + .../local-generator-history.service.spec.ts | 198 +++ .../local-generator-history.service.ts | 116 ++ .../src/tools/generator/history/options.ts | 10 + .../tools/generator/key-definition.spec.ts | 9 - .../src/tools/generator/key-definitions.ts | 11 +- .../passphrase-generator-policy.spec.ts | 51 + .../passphrase/passphrase-generator-policy.ts | 26 + .../passphrase-generator-strategy.spec.ts | 34 +- .../passphrase-generator-strategy.ts | 31 +- .../password-generator-policy.spec.ts | 55 + .../password/password-generator-policy.ts | 27 + .../password-generator-strategy.spec.ts | 34 +- .../password/password-generator-strategy.ts | 33 +- .../reduce-collection.operator.spec.ts | 33 + .../generator/reduce-collection.operator.ts | 20 + .../generator/state/classified-format.ts | 19 + .../state/data-packer.abstraction.ts | 2 +- .../state/padded-data-packer.spec.ts | 10 - .../generator/state/padded-data-packer.ts | 2 +- .../generator/state/secret-classifier.spec.ts | 18 +- .../generator/state/secret-classifier.ts | 22 +- .../state/secret-key-definition.spec.ts | 208 +++ .../generator/state/secret-key-definition.ts | 107 ++ .../generator/state/secret-state.spec.ts | 119 +- .../src/tools/generator/state/secret-state.ts | 242 ++-- .../state/user-encryptor.abstraction.ts | 18 +- .../state/user-key-encryptor.spec.ts | 77 +- .../generator/state/user-key-encryptor.ts | 33 +- .../catchall-generator-strategy.spec.ts | 46 +- .../username/catchall-generator-strategy.ts | 18 +- .../eff-username-generator-strategy.spec.ts | 46 +- .../eff-username-generator-strategy.ts | 18 +- .../forwarder-generator-strategy.spec.ts | 25 +- .../username/forwarder-generator-strategy.ts | 38 +- .../subaddress-generator-strategy.spec.ts | 46 +- .../username/subaddress-generator-strategy.ts | 18 +- .../src/vault/abstractions/cipher.service.ts | 14 + .../vault/abstractions/collection.service.ts | 2 + .../vault/models/domain/collection.spec.ts | 1 + .../cipher-bulk-update-collections.request.ts | 19 + .../src/vault/models/view/collection.view.ts | 23 + .../src/vault/services/cipher.service.spec.ts | 4 +- .../src/vault/services/cipher.service.ts | 58 +- .../fido2/fido2-client.service.spec.ts | 6 +- .../services/fido2/fido2-client.service.ts | 4 +- .../src/vault/services/sync/sync.service.ts | 28 +- libs/common/test.setup.ts | 8 + libs/components/src/icon/icon.stories.ts | 27 +- .../components/kitchen-sink-form.component.ts | 176 +++ .../components/kitchen-sink-main.component.ts | 117 ++ .../kitchen-sink-table.component.ts | 49 + .../kitchen-sink-toggle-list.component.ts | 34 + .../src/stories/kitchen-sink/index.ts | 1 + .../kitchen-sink-shared.module.ts | 114 ++ .../src/stories/kitchen-sink/kitchen-sink.mdx | 15 + .../kitchen-sink/kitchen-sink.stories.ts | 172 +++ .../cipher-with-collections.json.ts | 37 + .../lastpass-direct-import.service.ts | 7 +- .../src/services/import.service.spec.ts | 56 + libs/importer/src/services/import.service.ts | 6 +- libs/shared/tsconfig.libs.json | 1 + libs/tools/export/vault-export/README.md | 2 + .../vault-export-ui/jest.config.js | 13 + .../vault-export/vault-export-ui/package.json | 25 + .../export-scope-callout.component.html | 4 +- .../export-scope-callout.component.ts | 7 +- .../src}/components/export.component.ts | 3 +- .../vault-export/vault-export-ui/src/index.ts | 2 + .../vault-export-ui/tsconfig.json | 5 + .../vault-export-ui/tsconfig.spec.json | 3 + package-lock.json | 1143 +++++++++++++---- package.json | 24 +- tsconfig.eslint.json | 1 + tsconfig.json | 1 + 699 files changed, 17230 insertions(+), 8095 deletions(-) create mode 100644 apps/browser/.eslintrc.json create mode 100644 apps/browser/src/auth/background/service-factories/login-email-service.factory.ts create mode 100644 apps/browser/src/auth/background/service-factories/user-decryption-options-service.factory.ts rename apps/browser/src/platform/{ => popup}/services/browser-file-download.service.ts (93%) delete mode 100644 apps/browser/src/platform/services/browser-config.service.ts delete mode 100644 apps/browser/src/services/browser-send.service.ts create mode 100644 apps/browser/src/vault/services/vault-browser-state.service.spec.ts create mode 100644 apps/browser/src/vault/services/vault-browser-state.service.ts delete mode 100644 apps/cli/src/platform/services/cli-config.service.ts create mode 100644 apps/desktop/src/autofill/services/desktop-autofill-settings.service.ts rename {libs/common/src => apps/desktop/src/platform}/models/domain/window-state.ts (89%) create mode 100644 apps/desktop/src/platform/services/desktop-settings.service.ts create mode 100644 apps/desktop/src/platform/services/illegal-secure-storage.service.ts create mode 100644 apps/web/src/app/platform/web-environment.service.ts create mode 100644 apps/web/src/app/vault/org-vault/bulk-collection-assignment-dialog/bulk-collection-assignment-dialog.component.html create mode 100644 apps/web/src/app/vault/org-vault/bulk-collection-assignment-dialog/bulk-collection-assignment-dialog.component.ts create mode 100644 apps/web/src/app/vault/org-vault/bulk-collection-assignment-dialog/index.ts create mode 100644 libs/angular/src/platform/services/logging-error-handler.ts create mode 100644 libs/auth/src/common/abstractions/login-email.service.ts create mode 100644 libs/auth/src/common/abstractions/user-decryption-options.service.abstraction.ts create mode 100644 libs/auth/src/common/models/domain/user-decryption-options.ts create mode 100644 libs/auth/src/common/models/spec/fake-user-decryption-options.ts create mode 100644 libs/auth/src/common/models/spec/index.ts create mode 100644 libs/auth/src/common/services/login-email/login-email.service.ts create mode 100644 libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts create mode 100644 libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts create mode 100644 libs/common/spec/matchers/to-almost-equal.spec.ts create mode 100644 libs/common/spec/matchers/to-almost-equal.ts create mode 100644 libs/common/spec/observable-tracker.ts delete mode 100644 libs/common/src/auth/abstractions/login.service.ts delete mode 100644 libs/common/src/auth/models/domain/environment-urls.ts delete mode 100644 libs/common/src/auth/models/domain/user-decryption-options/key-connector-user-decryption-option.ts delete mode 100644 libs/common/src/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option.ts create mode 100644 libs/common/src/auth/services/auth.service.spec.ts create mode 100644 libs/common/src/auth/services/key-connector.service.spec.ts delete mode 100644 libs/common/src/auth/services/login.service.ts delete mode 100644 libs/common/src/platform/abstractions/config/config.service.abstraction.ts create mode 100644 libs/common/src/platform/abstractions/config/config.service.ts delete mode 100644 libs/common/src/platform/services/config/config.service.ts create mode 100644 libs/common/src/platform/services/config/default-config.service.ts create mode 100644 libs/common/src/platform/services/default-environment.service.spec.ts create mode 100644 libs/common/src/platform/services/default-environment.service.ts delete mode 100644 libs/common/src/platform/services/environment.service.spec.ts delete mode 100644 libs/common/src/platform/services/environment.service.ts create mode 100644 libs/common/src/state-migrations/migrations/44-move-user-decryption-options-to-state-provider.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/44-move-user-decryption-options-to-state-provider.ts create mode 100644 libs/common/src/state-migrations/migrations/45-merge-environment-state.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/45-merge-environment-state.ts create mode 100644 libs/common/src/state-migrations/migrations/46-delete-orphaned-biometric-prompt-data.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/46-delete-orphaned-biometric-prompt-data.ts create mode 100644 libs/common/src/state-migrations/migrations/47-move-desktop-settings.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/47-move-desktop-settings.ts create mode 100644 libs/common/src/state-migrations/migrations/48-move-ddg-to-state-provider.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/48-move-ddg-to-state-provider.ts create mode 100644 libs/common/src/state-migrations/migrations/49-move-account-server-configs.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/49-move-account-server-configs.ts create mode 100644 libs/common/src/state-migrations/migrations/50-move-key-connector-to-state-provider.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/50-move-key-connector-to-state-provider.ts create mode 100644 libs/common/src/state-migrations/migrations/51-move-remembered-email-to-state-providers.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/51-move-remembered-email-to-state-providers.ts create mode 100644 libs/common/src/state-migrations/migrations/52-delete-installed-version.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/52-delete-installed-version.ts create mode 100644 libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/53-migrate-device-trust-crypto-svc-to-state-providers.ts rename libs/common/src/state-migrations/migrations/{44-move-local-data-to-state-provider.spec.ts => 54-move-local-data-to-state-provider.spec.ts} (90%) rename libs/common/src/state-migrations/migrations/{44-move-local-data-to-state-provider.ts => 54-move-local-data-to-state-provider.ts} (96%) create mode 100644 libs/common/src/tools/generator/abstractions/generator-history.abstraction.ts create mode 100644 libs/common/src/tools/generator/history/generated-credential.spec.ts create mode 100644 libs/common/src/tools/generator/history/generated-credential.ts create mode 100644 libs/common/src/tools/generator/history/index.ts create mode 100644 libs/common/src/tools/generator/history/local-generator-history.service.spec.ts create mode 100644 libs/common/src/tools/generator/history/local-generator-history.service.ts create mode 100644 libs/common/src/tools/generator/history/options.ts create mode 100644 libs/common/src/tools/generator/passphrase/passphrase-generator-policy.spec.ts create mode 100644 libs/common/src/tools/generator/password/password-generator-policy.spec.ts create mode 100644 libs/common/src/tools/generator/reduce-collection.operator.spec.ts create mode 100644 libs/common/src/tools/generator/reduce-collection.operator.ts create mode 100644 libs/common/src/tools/generator/state/classified-format.ts create mode 100644 libs/common/src/tools/generator/state/secret-key-definition.spec.ts create mode 100644 libs/common/src/tools/generator/state/secret-key-definition.ts create mode 100644 libs/common/src/vault/models/request/cipher-bulk-update-collections.request.ts create mode 100644 libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts create mode 100644 libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts create mode 100644 libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts create mode 100644 libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts create mode 100644 libs/components/src/stories/kitchen-sink/index.ts create mode 100644 libs/components/src/stories/kitchen-sink/kitchen-sink-shared.module.ts create mode 100644 libs/components/src/stories/kitchen-sink/kitchen-sink.mdx create mode 100644 libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts create mode 100644 libs/importer/spec/test-data/bitwarden-json/cipher-with-collections.json.ts create mode 100644 libs/tools/export/vault-export/vault-export-ui/jest.config.js create mode 100644 libs/tools/export/vault-export/vault-export-ui/package.json rename libs/{angular/src/tools/export => tools/export/vault-export/vault-export-ui/src}/components/export-scope-callout.component.html (59%) rename libs/{angular/src/tools/export => tools/export/vault-export/vault-export-ui/src}/components/export-scope-callout.component.ts (86%) rename libs/{angular/src/tools/export => tools/export/vault-export/vault-export-ui/src}/components/export.component.ts (98%) create mode 100644 libs/tools/export/vault-export/vault-export-ui/src/index.ts create mode 100644 libs/tools/export/vault-export/vault-export-ui/tsconfig.json create mode 100644 libs/tools/export/vault-export/vault-export-ui/tsconfig.spec.json diff --git a/.checkmarx/config.yml b/.checkmarx/config.yml index 18b9be6a7e..e45e83fcac 100644 --- a/.checkmarx/config.yml +++ b/.checkmarx/config.yml @@ -7,5 +7,6 @@ checkmarx: scan: configs: sast: + presetName: "BW ASA Premium" # Exclude spec files, and test specific files filter: "!*.spec.ts,!**/spec/**,!apps/desktop/native-messaging-test-runner/**" diff --git a/.eslintrc.json b/.eslintrc.json index 47d45f23c3..671e7b2fab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -67,7 +67,7 @@ "pathGroupsExcludedImportTypes": ["builtin"] } ], - "rxjs-angular/prefer-takeuntil": "error", + "rxjs-angular/prefer-takeuntil": ["error", { "alias": ["takeUntilDestroyed"] }], "rxjs/no-exposed-subjects": ["error", { "allowProtected": true }], "no-restricted-syntax": [ "error", @@ -191,6 +191,15 @@ ] } }, + { + "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": { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bfce3c1547..bfad3f2628 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -83,6 +83,7 @@ apps/web/src/translation-constants.ts @bitwarden/team-platform-dev ## Autofill team files ## apps/browser/src/autofill @bitwarden/team-autofill-dev +apps/desktop/src/autofill @bitwarden/team-autofill-dev libs/common/src/autofill @bitwarden/team-autofill-dev ## Component Library ## diff --git a/.github/renovate.json b/.github/renovate.json index bd9ea0da5c..95fd2dc11e 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -16,6 +16,10 @@ "matchManagers": ["cargo"], "commitMessagePrefix": "[deps] Platform:" }, + { + "groupName": "napi", + "matchPackageNames": ["napi", "napi-build", "napi-derive"] + }, { "matchPackageNames": ["typescript", "zone.js"], "matchUpdateTypes": ["major", "minor"], diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 2c28d0cb52..e73f882bb4 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -444,7 +444,10 @@ jobs: macos-build: name: MacOS Build - runs-on: macos-13 + # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, + # as the newer versions will case the native modules to be incompatible with older macOS systems + # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules + runs-on: macos-11 needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} @@ -602,7 +605,10 @@ jobs: macos-package-github: name: MacOS Package GitHub Release Assets - runs-on: macos-13 + # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, + # as the newer versions will case the native modules to be incompatible with older macOS systems + # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules + runs-on: macos-11 needs: - browser-build - macos-build @@ -808,7 +814,10 @@ jobs: macos-package-mas: name: MacOS Package Prod Release Asset - runs-on: macos-13 + # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, + # as the newer versions will case the native modules to be incompatible with older macOS systems + # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules + runs-on: macos-11 needs: - browser-build - macos-build @@ -1006,7 +1015,10 @@ jobs: macos-package-dev: name: MacOS Package Dev Release Asset if: false # We need to look into how code signing works for dev - runs-on: macos-13 + # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, + # as the newer versions will case the native modules to be incompatible with older macOS systems + # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules + runs-on: macos-11 needs: - browser-build - macos-build diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index abd2538773..8576fb6760 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -299,7 +299,7 @@ jobs: keyvault: "bitwarden-ci" secrets: "github-pat-bitwarden-devops-bot-repo-scope" - - name: Trigger web vault deploy + - name: Trigger web vault deploy using GitHub Run ID uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} @@ -311,7 +311,7 @@ jobs: ref: 'main', inputs: { 'environment': 'USDEV', - 'branch-or-tag': 'main' + 'build-web-run-id': '${{ github.run_id }}' } }) diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index 2d784652a5..769e700588 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -27,6 +27,10 @@ on: description: "Debug mode" type: boolean default: true + build-web-run-id: + description: "Build-web workflow Run ID to use for artifact download" + type: string + required: false workflow_call: inputs: @@ -46,6 +50,10 @@ on: description: "Debug mode" type: boolean default: true + build-web-run-id: + description: "Build-web workflow Run ID to use for artifact download" + type: string + required: false permissions: deployments: write @@ -168,7 +176,20 @@ jobs: env: _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} steps: + - name: 'Download latest cloud asset using GitHub Run ID: ${{ inputs.build-web-run-id }}' + if: ${{ inputs.build-web-run-id }} + uses: bitwarden/gh-actions/download-artifacts@main + id: download-latest-artifacts + continue-on-error: true + with: + workflow: build-web.yml + path: apps/web + workflow_conclusion: success + run_id: ${{ inputs.build-web-run-id }} + artifacts: ${{ env._ENVIRONMENT_ARTIFACT }} + - name: 'Download latest cloud asset from branch/tag: ${{ inputs.branch-or-tag }}' + if: ${{ !inputs.build-web-run-id }} uses: bitwarden/gh-actions/download-artifacts@main id: download-artifacts continue-on-error: true @@ -249,7 +270,20 @@ jobs: keyvault: ${{ needs.setup.outputs.retrieve-secrets-keyvault }} secrets: "sa-bitwarden-web-vault-name,sp-bitwarden-web-vault-password,sp-bitwarden-web-vault-appid,sp-bitwarden-web-vault-tenant" + - name: 'Download latest cloud asset using GitHub Run ID: ${{ inputs.build-web-run-id }}' + if: ${{ inputs.build-web-run-id }} + uses: bitwarden/gh-actions/download-artifacts@main + id: download-latest-artifacts + continue-on-error: true + with: + workflow: build-web.yml + path: apps/web + workflow_conclusion: success + run_id: ${{ inputs.build-web-run-id }} + artifacts: ${{ env._ENVIRONMENT_ARTIFACT }} + - name: 'Download cloud asset from branch/tag: ${{ inputs.branch-or-tag }}' + if: ${{ !inputs.build-web-run-id }} uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-web.yml diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 20bffb956e..b9e2d7a8c8 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -393,7 +393,10 @@ jobs: macos-build: name: MacOS Build - runs-on: macos-13 + # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, + # as the newer versions will case the native modules to be incompatible with older macOS systems + # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules + runs-on: macos-11 needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} @@ -522,7 +525,10 @@ jobs: macos-package-github: name: MacOS Package GitHub Release Assets - runs-on: macos-13 + # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, + # as the newer versions will case the native modules to be incompatible with older macOS systems + # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules + runs-on: macos-11 needs: - setup - macos-build @@ -732,7 +738,10 @@ jobs: macos-package-mas: name: MacOS Package Prod Release Asset - runs-on: macos-13 + # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, + # as the newer versions will case the native modules to be incompatible with older macOS systems + # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules + runs-on: macos-11 needs: - setup - macos-build diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 3e07f7fc98..2da6daaa19 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -113,105 +113,12 @@ jobs: - name: Log out of Docker run: docker logout - - ghpages-deploy: - name: Create Deploy PR for GitHub Pages - runs-on: ubuntu-22.04 - needs: setup - env: - _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} - _TAG_VERSION: ${{ needs.setup.outputs.tag_version }} - _BRANCH: "v${{ needs.setup.outputs.release_version }}-deploy" - steps: - - name: Login to Azure - CI Subscription - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve bot secrets - id: retrieve-bot-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: bitwarden-ci - secrets: "github-pat-bitwarden-devops-bot-repo-scope" - - - name: Checkout GH pages repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - repository: bitwarden/web-vault-pages - path: ghpages-deployment - token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} - - - name: Download latest cloud asset - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-web.yml - path: assets - workflow_conclusion: success - branch: ${{ github.ref_name }} - artifacts: web-*-cloud-COMMERCIAL.zip - - - name: Dry Run - Download latest cloud asset - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-web.yml - path: assets - workflow_conclusion: success - branch: main - artifacts: web-*-cloud-COMMERCIAL.zip - - - name: Unzip build asset - working-directory: assets - run: unzip web-*-cloud-COMMERCIAL.zip - - - name: Create new branch - run: | - cd ${{ github.workspace }}/ghpages-deployment - git config user.name = "GitHub Action Bot" - git config user.email = "<>" - git config --global url."https://github.com/".insteadOf ssh://git@github.com/ - git config --global url."https://".insteadOf ssh:// - git checkout -b ${_BRANCH} - - - name: Copy build files - run: | - rm -rf ${{ github.workspace }}/ghpages-deployment/* - cp -Rf ${{ github.workspace }}/assets/build/* ghpages-deployment/ - - - name: Commit and push changes - working-directory: ghpages-deployment - run: | - git add . - git commit -m "Deploy Web v${_RELEASE_VERSION} to GitHub Pages" - git push --set-upstream origin ${_BRANCH} --force - - - name: Create GitHub Pages Deploy PR - working-directory: ghpages-deployment - env: - GITHUB_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} - run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then - gh pr create --title "Deploy v${_RELEASE_VERSION} to GitHub Pages" \ - --draft \ - --body "Deploying v${_RELEASE_VERSION}" \ - --base main \ - --head "${_BRANCH}" - else - gh pr create --title "Deploy v${_RELEASE_VERSION} to GitHub Pages" \ - --body "Deploying v${_RELEASE_VERSION}" \ - --base main \ - --head "${_BRANCH}" - fi - release: name: Create GitHub Release runs-on: ubuntu-22.04 needs: - setup - self-host - - ghpages-deploy steps: - name: Create GitHub deployment if: ${{ github.event.inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index ea9e69226a..878171cd17 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -10,8 +10,6 @@ on: pull_request_target: types: [opened, synchronize] -permissions: read-all - jobs: check-run: name: Check PR run @@ -22,6 +20,8 @@ jobs: runs-on: ubuntu-22.04 needs: check-run permissions: + contents: read + pull-requests: write security-events: write steps: @@ -43,7 +43,7 @@ jobs: additional_params: --report-format sarif --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 with: sarif_file: cx_result.sarif @@ -51,6 +51,9 @@ jobs: name: Quality scan runs-on: ubuntu-22.04 needs: check-run + permissions: + contents: read + pull-requests: write steps: - name: Check out repo diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 5aec22926d..246ca9a533 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -367,21 +367,27 @@ jobs: id: set-final-version-output run: | if [[ "${{ steps.bump-browser-version-override.outcome }}" = "success" ]]; then - echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT + echo "version_browser=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT elif [[ "${{ steps.bump-browser-version-automatic.outcome }}" = "success" ]]; then - echo "version=${{ steps.calculate-next-browser-version.outputs.version }}" >> $GITHUB_OUTPUT - elif [[ "${{ steps.bump-cli-version-override.outcome }}" = "success" ]]; then - echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT + echo "version_browser=${{ steps.calculate-next-browser-version.outputs.version }}" >> $GITHUB_OUTPUT + fi + + if [[ "${{ steps.bump-cli-version-override.outcome }}" = "success" ]]; then + echo "version_cli=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT elif [[ "${{ steps.bump-cli-version-automatic.outcome }}" = "success" ]]; then - echo "version=${{ steps.calculate-next-cli-version.outputs.version }}" >> $GITHUB_OUTPUT - elif [[ "${{ steps.bump-desktop-version-override.outcome }}" = "success" ]]; then - echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT + echo "version_cli=${{ steps.calculate-next-cli-version.outputs.version }}" >> $GITHUB_OUTPUT + fi + + if [[ "${{ steps.bump-desktop-version-override.outcome }}" = "success" ]]; then + echo "version_desktop=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT elif [[ "${{ steps.bump-desktop-version-automatic.outcome }}" = "success" ]]; then - echo "version=${{ steps.calculate-next-desktop-version.outputs.version }}" >> $GITHUB_OUTPUT - elif [[ "${{ steps.bump-web-version-override.outcome }}" = "success" ]]; then - echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT + echo "version_desktop=${{ steps.calculate-next-desktop-version.outputs.version }}" >> $GITHUB_OUTPUT + fi + + if [[ "${{ steps.bump-web-version-override.outcome }}" = "success" ]]; then + echo "version_web=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT elif [[ "${{ steps.bump-web-version-automatic.outcome }}" = "success" ]]; then - echo "version=${{ steps.calculate-next-web-version.outputs.version }}" >> $GITHUB_OUTPUT + echo "version_web=${{ steps.calculate-next-web-version.outputs.version }}" >> $GITHUB_OUTPUT fi - name: Check if version changed diff --git a/apps/browser/.eslintrc.json b/apps/browser/.eslintrc.json new file mode 100644 index 0000000000..ba96051183 --- /dev/null +++ b/apps/browser/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "env": { + "browser": true, + "webextensions": true + }, + "overrides": [ + { + "files": ["src/**/*.ts"], + "excludedFiles": [ + "src/**/{content,popup,spec}/**/*.ts", + "src/**/autofill/{notification,overlay}/**/*.ts", + "src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts", + "src/**/*.spec.ts" + ], + "rules": { + "no-restricted-globals": [ + "error", + { + "name": "window", + "message": "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead." + } + ] + } + } + ] +} diff --git a/apps/browser/package.json b/apps/browser/package.json index b03469bbb7..d06eadf58d 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2024.3.0", + "version": "2024.3.1", "scripts": { "build": "webpack", "build:mv3": "cross-env MANIFEST_VERSION=3 webpack", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 134c045f78..5e3a0152a5 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2386,12 +2386,6 @@ "message": "الاتحاد الأوروبي", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 0dc43fedec..5d17d567fc 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2386,12 +2386,6 @@ "message": "AB", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Müraciət rədd edildi. Bu səhifəyə baxmaq üçün icazəniz yoxdur." }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index f8454d4bd5..f05102d29f 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2386,12 +2386,6 @@ "message": "ЕС", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Доступ забаронены. У вас не дастаткова правоў для прагляду гэтай старонкі." }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 8bc3b2d026..e96a48b3f0 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2386,12 +2386,6 @@ "message": "ЕС", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Отказан достъп. Нямате право за преглед на страницата." }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index c5ba112f3b..cfaed770c1 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index d1466fb82a..9260f5c902 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 1bc35914d7..b77edf5611 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2386,12 +2386,6 @@ "message": "UE", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Accés denegat. No teniu permís per veure aquesta pàgina." }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index ca81f2809a..bc2d7e8fd8 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Přístup byl odepřen. Nemáte oprávnění k zobrazení této stránky." }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 55c6042ae8..665470b512 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2386,12 +2386,6 @@ "message": "UE", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Mynediad wedi ei wrthod. Does gennych chi ddim caniatâd i weld y dudalen hon." }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index f1a710d17f..ab4c4e5b6e 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -709,7 +709,7 @@ "message": "Vis indstillinger i kontekstmenuen" }, "contextMenuItemDesc": { - "message": "Brug et sekundært klik for at få adgang til adgangskodegenerering og matchende logins til hjemmesiden." + "message": "Brug et sekundært klik for at tilgå adgangskodegenerering og matchende logins til webstedet." }, "contextMenuItemDescAlt": { "message": "Brug et sekundært klik for at få adgang til adgangskodegenerering og matchende logins til webstedet. Gælder alle indloggede konti." @@ -1033,7 +1033,7 @@ "message": "Server URL" }, "apiUrl": { - "message": "API server URL" + "message": "API-server URL" }, "webVaultUrl": { "message": "Web-boks server URL" @@ -1574,7 +1574,7 @@ "message": "Advarsel: Dette er en ikke-sikret HTTP side, og alle indsendte oplysninger kan potentielt ses og ændres af andre. Dette login blev oprindeligt gemt på en sikker (HTTPS) side." }, "insecurePageWarningFillPrompt": { - "message": "Do you still wish to fill this login?" + "message": "Ønsker dette login stadig udfyldt?" }, "autofillIframeWarning": { "message": "Formularen hostes af et andet domæne end URI'en for det gemte login. Vælg OK for at autoudfylde alligevel, eller Afbryd for at stoppe." @@ -1712,7 +1712,7 @@ "message": "Biometri mislykkedes" }, "biometricsFailedDesc": { - "message": "Biometri kan ikke fuldføres, overvej at bruge en hovedadgangskode eller logge ud og ind igen. Fortsætter problemet, kontakt Bitwarden-supporten." + "message": "Biometri kan ikke gennemføres. Overvej at bruge en hovedadgangskode eller at logge ud. Fortsætter problemet, kontakt Bitwarden-supporten." }, "nativeMessaginPermissionErrorTitle": { "message": "Tilladelse ikke givet" @@ -2027,7 +2027,7 @@ "message": "Minutter" }, "vaultTimeoutPolicyInEffect": { - "message": "Organisationspolitikker har sat maks. tilladt boks-timeout. til $HOURS$ time(r) og $MINUTES$ minut(ter).", + "message": "Organisationspolitikkerne har fastsat den maksimalt tilladte boks-timeout til $HOURS$ time(r) og $MINUTES$ minut(ter).", "placeholders": { "hours": { "content": "$1", @@ -2314,7 +2314,7 @@ "message": "Sådan autoudfyldes" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "Vælg et emne fra denne skærm, brug genvejen $COMMAND$ eller udforsk andre valgmuligheder i Indstillinger.", "placeholders": { "command": { "content": "$1", @@ -2323,7 +2323,7 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "Vælg et emne fra denne skærm eller udforsk andre valgmuligheder i Indstillinger." }, "gotIt": { "message": "Forstået" @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Adgang nægtet. Nødvendig tilladelse til at se siden mangler." }, @@ -2706,7 +2700,7 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Start DUO og følg trinene for at fuldføre indlogningen." + "message": "Start Duo og følg trinnene for at fuldføre indlogningen." }, "duoRequiredForAccount": { "message": "Duo-totrinsindlogning kræves for kontoen." @@ -2718,7 +2712,7 @@ "message": "Pop ud-udvidelse" }, "launchDuo": { - "message": "Start DUO" + "message": "Start Duo" }, "importFormatError": { "message": "Data er ikke korrekt formateret. Tjek importfilen og forsøg igen." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 7a013add6e..0406953936 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -327,7 +327,7 @@ "message": "Passwort" }, "totp": { - "message": "Authenticator secret" + "message": "Authentifikator-Geheimnis" }, "passphrase": { "message": "Passphrase" @@ -522,16 +522,16 @@ "message": "Die Felder dieser Seite konnten nicht automatisch ausgefüllt werden. Bitte Nutzernamen und/oder Passwort manuell kopieren." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "QR-Code kann nicht von der aktuellen Webseite gescannt werden" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "Authentifizierungsschlüssel hinzugefügt" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "Den Authentifizierungs-QR-Code von der aktuellen Webseite scannen" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Authentifizierungsschlüssel kopieren (TOTP)" }, "loggedOut": { "message": "Ausgeloggt" @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Zugriff verweigert. Du hast keine Berechtigung, diese Seite anzuzeigen." }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 01c4fe10a6..64014298e2 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -2386,12 +2386,6 @@ "message": "ΕΕ", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Δεν επιτρέπεται η πρόσβαση. Δεν έχετε άδεια για να δείτε αυτή τη σελίδα." }, diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index cf0ce67980..d802d27700 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, @@ -3005,5 +2999,11 @@ "saveCipherAttemptFailed": { "message": "Error saving credentials. Check console for details.", "description": "Notification message for when saving credentials has failed." + }, + "removePasskey": { + "message": "Remove passkey" + }, + "passkeyRemoved": { + "message": "Passkey removed" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index d2182a0b4e..26af3b5f71 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 8ab1d1fc58..ddbc3f41c9 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 58172b1a7a..b55c64c9f7 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2386,12 +2386,6 @@ "message": "Unión Europea", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Acceso denegado. No tiene permiso para ver esta página." }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 001588d5bc..b7e8b2419e 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2386,12 +2386,6 @@ "message": "EL", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Ligipääs keelatud. Sul pole lubatud seda lehekülge vaadata." }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 64733c6c68..ac30dc4b28 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 0032767386..373fc5a8d0 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2386,12 +2386,6 @@ "message": "اروپا", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "دسترسی رد شد. شما اجازه مشاهده این صفحه را ندارید." }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index a1955c205c..343c22d5d0 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Pääsy estetty. Sinulla ei ole oikeutta avata sivua." }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index f84d451ced..2a09430c27 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 7abc33e0f2..73e00ba489 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -95,7 +95,7 @@ "message": "Saisie automatique de l'identifiant" }, "autoFillCard": { - "message": "Saisie automatique de la carte" + "message": "Saisie automatique de la carte de paiement" }, "autoFillIdentity": { "message": "Saisie automatique de l'identité" @@ -110,7 +110,7 @@ "message": "Aucun identifiant correspondant" }, "noCards": { - "message": "Aucune carte" + "message": "Aucune carte de paiement" }, "noIdentities": { "message": "Aucune identité" @@ -119,7 +119,7 @@ "message": "Ajouter un identifiant" }, "addCardMenu": { - "message": "Ajouter une carte" + "message": "Ajouter une carte de paiement" }, "addIdentityMenu": { "message": "Ajouter une identité" @@ -131,7 +131,7 @@ "message": "Connectez-vous à votre coffre" }, "autoFillInfo": { - "message": "Il n'y a pas d'identifiants disponibles pour la saisie automatique dans l'onglet actuel." + "message": "Il n'y a pas d'identifiants disponibles pour la saisie automatique de l'onglet actuel du navigateur." }, "addLogin": { "message": "Ajouter un identifiant" @@ -223,7 +223,7 @@ "message": "Centre d'aide Bitwarden" }, "communityForums": { - "message": "Explorez les forums de la communauté Bitwarden" + "message": "Explorer les forums de la communauté Bitwarden" }, "contactSupport": { "message": "Contacter le support Bitwarden" @@ -312,7 +312,7 @@ "message": "Modifier" }, "view": { - "message": "Voir" + "message": "Afficher" }, "noItemsInList": { "message": "Aucun identifiant à afficher." @@ -327,7 +327,7 @@ "message": "Mot de passe" }, "totp": { - "message": "Application d'authentification" + "message": "Secret de l'Authentificateur" }, "passphrase": { "message": "Phrase de passe" @@ -360,7 +360,7 @@ "message": "Site web" }, "toggleVisibility": { - "message": "Afficher/Masquer" + "message": "Permuter la visibilité" }, "manage": { "message": "Gérer" @@ -522,16 +522,16 @@ "message": "Impossible de saisir automatiquement l'élément sélectionné sur cette page. Essayez plutôt le copier-coller." }, "totpCaptureError": { - "message": "Impossible de scanner le code QR à partir de la page web actuelle" + "message": "Impossible de scanner le QR code à partir de la page web actuelle" }, "totpCaptureSuccess": { - "message": "Clé d'authentification ajoutée" + "message": "Clé de l'Authentificateur ajoutée" }, "totpCapture": { - "message": "Scanner le code QR à partir de la page web actuelle" + "message": "Scanner le QR code de l'authentificateur à partir de la page web actuelle" }, "copyTOTP": { - "message": "Copier la clé TOTP" + "message": "Copier la clé de l'Authentificateur (TOTP)" }, "loggedOut": { "message": "Déconnecté" @@ -644,25 +644,25 @@ "description": "This is the folder for uncategorized items" }, "enableAddLoginNotification": { - "message": "Demander à ajouter un identifiant" + "message": "Demander d'ajouter un identifiant" }, "addLoginNotificationDesc": { - "message": "Demander à ajouter un élément si aucun n'est trouvé dans votre coffre." + "message": "Demander d'ajouter un élément si aucun n'est trouvé dans votre coffre." }, "addLoginNotificationDescAlt": { - "message": "Demandez d'ajouter un élément s'il celui-ci n'est pas trouvé dans votre coffre. S'applique à tous les comptes connectés." + "message": "Demande l'ajout d'un élément si celui-ci n'est pas trouvé dans votre coffre. S'applique à tous les comptes connectés." }, "showCardsCurrentTab": { - "message": "Afficher les cartes de paiement sur page d'Onglet" + "message": "Afficher les cartes de paiement sur la Page d'onglet" }, "showCardsCurrentTabDesc": { - "message": "Lister les éléments de la carte sur la page de l'onglet pour faciliter la saisie automatique." + "message": "Liste les éléments des cartes de paiement sur la Page d'onglet pour faciliter la saisie automatique." }, "showIdentitiesCurrentTab": { - "message": "Afficher les identités sur page d'Onglet" + "message": "Afficher les identités sur la page d'onglet" }, "showIdentitiesCurrentTabDesc": { - "message": "Lister les éléments d'identité sur la page de l'onglet pour faciliter la saisie automatique." + "message": "Liste les éléments d'identité sur la Page d'onglet pour faciliter la saisie automatique." }, "clearClipboard": { "message": "Effacer le presse-papiers", @@ -679,19 +679,19 @@ "message": "Enregistrer" }, "enableChangedPasswordNotification": { - "message": "Demander à mettre à jour un identifiant existant" + "message": "Demander de mettre à jour un identifiant existant" }, "changedPasswordNotificationDesc": { - "message": "Demander à mettre à jour le mot de passe d'un identifiant lorsqu'un changement est détecté sur un site Web." + "message": "Demande la mise à jour du mot de passe d'un identifiant lorsqu'un changement est détecté sur un site Web." }, "changedPasswordNotificationDescAlt": { - "message": "Demander à mettre à jour le mot de passe d'un identifiant lorsqu'un changement est détecté sur un site web. S'applique à tous les comptes connectés." + "message": "Demande la mise à jour du mot de passe d'un identifiant lorsqu'un changement est détecté sur un site web. S'applique à tous les comptes connectés." }, "enableUsePasskeys": { - "message": "Demander à enregistrer et utiliser les clés d'accès" + "message": "Demander d'enregistrer et d'utiliser les clés d'identification (passkeys)" }, "usePasskeysDesc": { - "message": "Demander d'enregistrer de nouvelles clés d'accès ou bien de se connecter à l'aide de clés stockées dans votre coffre. S'applique à tous les comptes connectés." + "message": "Demande l'enregistrement de nouvelles clés d'identification (passkeys) ou la connexion à l'aide des clés d'identification (passkeys) stockées dans votre coffre. S'applique à tous les comptes connectés." }, "notificationChangeDesc": { "message": "Souhaitez-vous mettre à jour ce mot de passe dans Bitwarden ?" @@ -719,7 +719,7 @@ "description": "Default URI match detection for auto-fill." }, "defaultUriMatchDetectionDesc": { - "message": "Choisissez la manière de gestion par défaut de la détection de correspondance URI pour les identifiants lors de l'exécution d'actions telles que la saisie automatique." + "message": "Choisit la manière dont la détection des correspondances URI est gérée par défaut pour les connexions lors d'actions telles que la saisie automatique." }, "theme": { "message": "Thème" @@ -802,7 +802,7 @@ "message": "En savoir plus" }, "authenticatorKeyTotp": { - "message": "Clé d'authentification (TOTP)" + "message": "Clé de l'Authentificateur (TOTP)" }, "verificationCodeTotp": { "message": "Code de vérification (TOTP)" @@ -841,7 +841,7 @@ "message": "La taille maximale du fichier est de 500 Mo." }, "featureUnavailable": { - "message": "Fonctionnalité non disponible" + "message": "Fonctionnalité indisponible" }, "encryptionKeyMigrationRequired": { "message": "Migration de la clé de chiffrement nécessaire. Veuillez vous connecter sur le coffre web pour mettre à jour votre clé de chiffrement." @@ -907,7 +907,7 @@ "message": "Actualisation terminée" }, "enableAutoTotpCopy": { - "message": "Copier TOTP automatiquement" + "message": "Copier le TOTP automatiquement" }, "disableAutoTotpCopyDesc": { "message": "Si un identifiant possède une clé d'authentification, copie le code de vérification TOTP dans votre presse-papiers lorsque vous saisissez automatiquement l'identifiant." @@ -967,7 +967,7 @@ "message": "Authentifier WebAuthn" }, "loginUnavailable": { - "message": "Identifiant non disponible" + "message": "Identifiant indisponible" }, "noTwoStepProviders": { "message": "Ce compte dispose d'une authentification à deux facteurs de configurée, cependant, aucun des fournisseurs à deux facteurs configurés n'est pris en charge par ce navigateur web." @@ -1887,7 +1887,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendShareDesc": { - "message": "Copier dans le presse-papiers le lien de ce Send lors de l'enregistrement.", + "message": "Copier le lien de ce Send dans le presse-papiers lors de l'enregistrement.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -2166,7 +2166,7 @@ "message": "Service" }, "forwardedEmail": { - "message": "Alias d'email transféré" + "message": "Alias de courriel transféré" }, "forwardedEmailDesc": { "message": "Générer un alias de courriel avec un service de transfert externe." @@ -2386,12 +2386,6 @@ "message": "UE", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Accès refusé. Vous n'avez pas l'autorisation de voir cette page." }, @@ -2792,55 +2786,55 @@ "message": "Confirmez le mot de passe du fichier" }, "typePasskey": { - "message": "Clé d'accès" + "message": "Clé d'identification (passkey)" }, "passkeyNotCopied": { - "message": "La clé d'accès ne sera pas copiée" + "message": "La clé d'identification (passkey) ne sera pas copiée" }, "passkeyNotCopiedAlert": { - "message": "La clé d'accès ne sera pas présente sur l'entrée clonée. Continuer quand même ?" + "message": "La clé d'identification (passkey) ne sera pas copiée dans l'élément cloné. Voulez-vous continuer à cloner cet élément ?" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { - "message": "Le site requiert une vérification qui n'est pas supportée pour les comptes sans mot de passe maître." + "message": "Vérification requise par le site initiateur. Cette fonctionnalité n'est pas encore implémentée pour les comptes sans mot de passe principal." }, "logInWithPasskey": { - "message": "Se connecter avec une clé d'accès ?" + "message": "Se connecter avec une clé d'identification (passkey) ?" }, "passkeyAlreadyExists": { - "message": "Une clé d'accès existe déjà pour cette application." + "message": "Une clé d'identification (passkey) existe déjà pour cette application." }, "noPasskeysFoundForThisApplication": { - "message": "Aucune clé d'accès trouvée pour cette application." + "message": "Aucune clé d'identification (passkey) trouvée pour cette application." }, "noMatchingPasskeyLogin": { - "message": "Vous n'avez aucun élément correspondant à ce site." + "message": "Vous n'avez pas d'identifiant correspondant à ce site." }, "confirm": { "message": "Confirmer" }, "savePasskey": { - "message": "Enregistrer la clé d'accès" + "message": "Enregistrer la clé d'identification (passkey)" }, "savePasskeyNewLogin": { - "message": "Enregistrer comme nouvel élément" + "message": "Enregistrer la clé d'identification (passkey) comme nouvel identifiant" }, "choosePasskey": { - "message": "Choisissez l'élément à associer à la clé d'accès" + "message": "Choisissez cette clé d'identification (passkey) pour l'enregistrer avec cet identifiant" }, "passkeyItem": { - "message": "Élément avec clé d'accès" + "message": "Élément clé d'identification (passkey)" }, "overwritePasskey": { - "message": "Écraser la clé d'accès existante ?" + "message": "Écraser la clé d'identification (passkey) ?" }, "overwritePasskeyAlert": { - "message": "Cet élément contient déjà une clé d'accès. Voulez-vous vraiment l'écraser ?" + "message": "Cet élément contient déjà une clé d'identification (passkey). Êtes-vous sûr de vouloir écraser la clé d'identification (passkey) actuelle ?" }, "featureNotSupported": { "message": "Fonctionnalité non supportée" }, "yourPasskeyIsLocked": { - "message": "Authentification requise pour utiliser cette clé d'accès. Veuillez vérifier votre identité pour continuer." + "message": "Authentification requise pour utiliser une clé d'identification (passkey). Vérifiez votre identité pour continuer." }, "multifactorAuthenticationCancelled": { "message": "Authentification multi-facteurs annulée" @@ -2995,15 +2989,15 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Identifiants enregistrés avec succès !", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Identifiants mis à jour avec succès !", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Erreur lors de l'enregistrement des identifiants. Consultez la console pour plus de détails.", "description": "Notification message for when saving credentials has failed." } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 97c8fa424b..4a37361f29 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 0df463d840..d902be6af0 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 6b27b5ff51..767edcfb95 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 41382ae8a5..b79477d83c 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Pristup odbijen. Nemaš prava vidjeti ovu stranicu." }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index f58743f4ad..fe4292d9c0 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "A hozzáférés megtagadásra került. Nincs jogosultság az oldal megtekintésére." }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 0c21ec8f3c..44f6be8cef 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -20,7 +20,7 @@ "message": "Masuk" }, "enterpriseSingleSignOn": { - "message": "Sistem Masuk Tunggal Perusahaan" + "message": "SSO Perusahaan" }, "cancel": { "message": "Batal" @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index cf4e32436c..ac6bcc9cb5 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Accesso negato. Non hai i permessi necessari per visualizzare questa pagina." }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index f7be05d670..990775c084 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "アクセスが拒否されました。このページを表示する権限がありません。" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 93cf353696..fdcd46bc3c 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 97c8fa424b..4a37361f29 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index b065c2f245..20fb2ea458 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 2943c7234d..394534d6b0 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 703eb9ea6d..fe73a8a28a 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2386,12 +2386,6 @@ "message": "ES", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Prieiga uždrausta. Neturi teisės peržiūrėti šį puslapį." }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index a692685b88..83f3de2556 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2386,12 +2386,6 @@ "message": "ES", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Piekļuve liegta. Nav nepieciešamo atļauju, lai skatītu šo lapu." }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 6c864d3db3..c2d1006694 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index d396ec9caf..7ddbe00732 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 97c8fa424b..4a37361f29 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 6cb1213585..d7a8345a23 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 97c8fa424b..4a37361f29 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 8fbce3c922..c28b99b7c2 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Toegang geweigerd. Je hebt geen toestemming om deze pagina te bekijken." }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 97c8fa424b..4a37361f29 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 97c8fa424b..4a37361f29 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index c72c837a86..e4c3b7b171 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2386,12 +2386,6 @@ "message": "UE", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Odmowa dostępu. Nie masz uprawnień do przeglądania tej strony." }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 8e0a7fb25b..3445a3ff5f 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2386,12 +2386,6 @@ "message": "Europa", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Acesso negado. Você não tem permissão para ver esta página." }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index fdd8d04945..117c5be6b4 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2386,12 +2386,6 @@ "message": "UE", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Acesso negado. Não tem permissão para visualizar esta página." }, @@ -2919,7 +2913,7 @@ "message": "Mudar de conta" }, "switchAccounts": { - "message": "Mudar de contas" + "message": "Mudar de conta" }, "switchToAccount": { "message": "Mudar para conta" diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index ce30d10790..4851add018 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Acces refuzat. Nu aveți permisiunea de a vizualiza această pagină." }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 6a6398d445..fb4e2abaac 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2386,12 +2386,6 @@ "message": "Европа", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Доступ запрещен. У вас нет разрешения на просмотр этой страницы." }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 6f73892b14..ca466ef7bb 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 9f5b07fb98..382dc82245 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2386,12 +2386,6 @@ "message": "EÚ", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Prístup zamietnutý. Nemáte oprávnenie na zobrazenie tejto stránky." }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index e0fcda1359..e11a56acde 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index ab4f07c5cb..d598263e91 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Одбијен приступ. Немате дозволу да видите ову страницу." }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 301ec51cba..082fbac350 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -494,7 +494,7 @@ "message": "Ditt nya konto har skapats! Du kan logga in nu." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Du är nu inloggad" }, "youMayCloseThisWindow": { "message": "Du kan stänga detta fönster" @@ -522,7 +522,7 @@ "message": "Kunde inte automatiskt fylla i det valda objektet på den här webbsidan. Klipp/klistra informationen istället." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "Det går inte att skanna QR-koden från den aktuella webbsidan" }, "totpCaptureSuccess": { "message": "Authenticator key added" @@ -868,7 +868,7 @@ "message": "1 GB lagring av krypterade filer." }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "Premium-alternativ för tvåstegsverifiering, såsom YubiKey och Duo." }, "ppremiumSignUpReports": { "message": "Lösenordshygien, kontohälsa och dataintrångsrapporter för att hålla ditt valv säkert." @@ -1993,7 +1993,7 @@ "message": "Ditt huvudlösenord ändrades nyligen av en administratör i din organisation. För att få tillgång till valvet måste du uppdatera det nu. Om du fortsätter kommer du att loggas ut från din nuvarande session, vilket kräver att du loggar in igen. Aktiva sessioner på andra enheter kan komma att vara aktiva i upp till en timme." }, "updateWeakMasterPasswordWarning": { - "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "Ditt huvudlösenord följer inte ett eller flera av din organisations regler. För att komma åt ditt valv så måste du ändra ditt huvudlösenord nu. Om du gör det kommer du att loggas du ut ur din nuvarande session så du måste logga in på nytt. Aktiva sessioner på andra enheter kommer fortsatt vara aktiva i upp till en timme." }, "resetPasswordPolicyAutoEnroll": { "message": "Automatiskt deltagande" @@ -2009,11 +2009,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": "Din organisations behörigheter uppdaterades, vilket kräver att du anger ett huvudlösenord.", "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": "Din organisation kräver att du anger ett huvudlösenord.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { @@ -2215,7 +2215,7 @@ "message": "Serverversion" }, "selfHostedServer": { - "message": "self-hosted" + "message": "självhostad" }, "thirdParty": { "message": "Tredje part" @@ -2371,7 +2371,7 @@ "message": "Uncheck if using a public device" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Godkänn från din andra enhet" }, "requestAdminApproval": { "message": "Be om godkännande från administratör" @@ -2380,18 +2380,12 @@ "message": "Godkänn med huvudlösenord" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "Organisationens SSO-identifierare krävs." }, "eu": { "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, @@ -2402,10 +2396,10 @@ "message": "Display" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Ditt konto har skapats!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Godkännande från administratör har begärts" }, "adminApprovalRequestSentToAdmins": { "message": "Din begäran har skickats till din administratör." @@ -2420,13 +2414,13 @@ "message": "Inloggning godkänd" }, "userEmailMissing": { - "message": "User email missing" + "message": "Användarens e-postadress saknas" }, "deviceTrusted": { "message": "Device trusted" }, "inputRequired": { - "message": "Input is required." + "message": "Inmatning är obligatoriskt." }, "required": { "message": "obligatoriskt" @@ -2526,7 +2520,7 @@ "message": "Undermeny" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "Växla synlig/dold", "description": "Toggling an expand/collapse state." }, "filelessImport": { @@ -2550,7 +2544,7 @@ "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "Data successfully imported!", + "message": "Data har importerats till ditt valv!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { @@ -2577,7 +2571,7 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "skipToContent": { - "message": "Skip to content" + "message": "Hoppa till innehåll" }, "bitwardenOverlayButton": { "message": "Bitwarden automatisk ifyllnadsmenyknapp", @@ -2596,11 +2590,11 @@ "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "Lås upp konto", "description": "Button text to display in overlay when the account is locked." }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "Fyll i uppgifter för", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { @@ -2624,7 +2618,7 @@ "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { - "message": "Turn on" + "message": "Aktivera" }, "ignore": { "message": "Ignorera" @@ -2634,7 +2628,7 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "Fel vid import" }, "importErrorDesc": { "message": "Det uppstod ett problem med den data du försökte importera. Åtgärda de fel som anges nedan i din källfil och försök igen." @@ -2646,10 +2640,10 @@ "message": "Beskrivning" }, "importSuccess": { - "message": "Data successfully imported" + "message": "Data har importerats till ditt valv" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "Sammanlagt $AMOUNT$ objekt importerades.", "placeholders": { "amount": { "content": "$1", @@ -2670,10 +2664,10 @@ "message": "Verifiera med biometri" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Väntar på bekräftelse" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "Det gick inte att slutföra biometri." }, "needADifferentMethod": { "message": "Behöver du en annan metod?" @@ -2688,16 +2682,16 @@ "message": "Använd biometri" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Ange verifieringskoden som skickades till din e-postadress." }, "resendCode": { "message": "Skicka kod igen" }, "total": { - "message": "Total" + "message": "Totalt" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "Du importerar data till $ORGANIZATION$. Din data kan komma att delas med medlemmar i den här organisationen. Vill du fortsätta?", "placeholders": { "organization": { "content": "$1", @@ -2718,19 +2712,19 @@ "message": "Popout extension" }, "launchDuo": { - "message": "Launch Duo" + "message": "Starta Duo" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "Datan är inte korrekt formaterad. Vänligen kontrollera din importerade fil och försök igen." }, "importNothingError": { "message": "Ingenting har importerats." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "Ett fel uppstod vid dekryptering av den exporterade filen. Din krypteringsnyckel matchar inte krypteringsnyckeln som användes för att exportera datan." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "Ogiltigt fillösenord, använd lösenordet du angav när du skapade exportfilen." }, "importDestination": { "message": "Importdestination" @@ -2755,13 +2749,13 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "Filen innehåller otilldelade objekt." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "Välj importfilens format" }, "selectImportFile": { - "message": "Select the import file" + "message": "Välj importfilen" }, "chooseFile": { "message": "Välj fil" @@ -2770,10 +2764,10 @@ "message": "Ingen fil vald" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "eller kopiera och klistra in innehållet från filen" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "Instruktioner för $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -2783,13 +2777,13 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "Bekräfta importering av valv" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "Den här filen är lösenordsskyddad. Ange lösenordet för att importera data." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Bekräfta fillösenord" }, "typePasskey": { "message": "Lösennyckel" @@ -2837,7 +2831,7 @@ "message": "Detta objekt innehåller redan en lösennyckel. Är du säker på att du vill skriva över nuvarande lösennyckeln?" }, "featureNotSupported": { - "message": "Feature not yet supported" + "message": "Funktionen stöds ännu inte" }, "yourPasskeyIsLocked": { "message": "Autentisering krävs för att använda lösennyckel. Verifiera din identitet för att fortsätta." @@ -2913,19 +2907,19 @@ "message": "Samling" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "Sätt in YubiKey som associeras med ditt LastPass-konto i din dators USB-port, tryck sedan på knappen." }, "switchAccount": { - "message": "Switch account" + "message": "Byt konto" }, "switchAccounts": { - "message": "Switch accounts" + "message": "Byt konto" }, "switchToAccount": { - "message": "Switch to account" + "message": "Byt till konto" }, "activeAccount": { - "message": "Active account" + "message": "Aktivt konto" }, "availableAccounts": { "message": "Tillgängliga konton" @@ -2952,10 +2946,10 @@ "message": "Använd din enhet eller hårdvarunyckel" }, "justOnce": { - "message": "Just once" + "message": "Bara en gång" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "Alltid för denna webbplats" }, "domainAddedToExcludedDomains": { "message": "$DOMAIN$ added to excluded domains.", @@ -2991,7 +2985,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": "Gör till standard", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 97c8fa424b..4a37361f29 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index e52aecbeda..011b7983d4 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index c7839fb4d9..391fef8ddc 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2386,12 +2386,6 @@ "message": "AB", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Erişim engellendi. Bu sayfayı görüntüleme iznine sahip değilsiniz." }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 4ab4067914..98dabb597d 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2386,12 +2386,6 @@ "message": "ЄС", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Доступ заборонено. У вас немає дозволу на перегляд цієї сторінки." }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index f02f1127c7..3a7cf5a794 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index cd189db75f..1c269640c8 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1500,7 +1500,7 @@ "message": "无效 PIN 码。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "无效的 PIN 输入尝试次数过多,正在退出登录。" + "message": "无效的 PIN 输入尝试次数过多,正在注销。" }, "unlockWithBiometrics": { "message": "使用生物识别解锁" @@ -1742,7 +1742,7 @@ "message": "Bitwarden 将不会询问是否为这些域名保存登录信息。您必须刷新页面才能使更改生效。" }, "excludedDomainsDescAlt": { - "message": "Bitwarden 不会询问保存所有已登录的账户的这些域名的登录信息。您必须刷新页面才能使更改生效。" + "message": "Bitwarden 不会询问保存所有已登录的账户的这些域名的登录信息。必须刷新页面才能使更改生效。" }, "excludedDomainsInvalidDomain": { "message": "$DOMAIN$ 不是一个有效的域名", @@ -2314,7 +2314,7 @@ "message": "如何自动填充" }, "autofillSelectInfoWithCommand": { - "message": "从此界面选择一个项目,使用快捷方式 $COMMAND$,或探索设置中的其他选项。", + "message": "从此界面选择一个项目,使用快捷键 $COMMAND$,或探索设置中的其他选项。", "placeholders": { "command": { "content": "$1", @@ -2335,10 +2335,10 @@ "message": "自动填充键盘快捷键" }, "autofillShortcutNotSet": { - "message": "未设置自动填充快捷方式。请在浏览器设置中更改此设置。" + "message": "未设置自动填充快捷键。可在浏览器的设置中更改它。" }, "autofillShortcutText": { - "message": "自动填充快捷方式为: $COMMAND$。在浏览器设置中更改此项。", + "message": "自动填充快捷键为:$COMMAND$。可在浏览器的设置中更改它。", "placeholders": { "command": { "content": "$1", @@ -2386,12 +2386,6 @@ "message": "欧盟", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "访问被拒绝。您没有权限查看此页面。" }, @@ -2934,7 +2928,7 @@ "message": "已达到账户上限。请注销一个账户后再添加其他账户。" }, "active": { - "message": "已生效" + "message": "活动的" }, "locked": { "message": "已锁定" @@ -2967,7 +2961,7 @@ } }, "commonImportFormats": { - "message": "通用格式", + "message": "常规格式", "description": "Label indicating the most common import formats" }, "overrideDefaultBrowserAutofillTitle": { @@ -2975,7 +2969,7 @@ "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "忽略此设置可能会导致 Bitwarden 自动填充菜单与浏览器自带功能产生冲突。", + "message": "忽略此选项可能会导致 Bitwarden 自动填充菜单与浏览器自带功能产生冲突。", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 80e05c7c1c..fdd6f4639b 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -327,7 +327,7 @@ "message": "密碼" }, "totp": { - "message": "Authenticator secret" + "message": "程式產生驗證碼" }, "passphrase": { "message": "密碼短語" @@ -372,7 +372,7 @@ "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "設定中設定解鎖" }, "rateExtension": { "message": "為本套件評分" @@ -494,10 +494,10 @@ "message": "帳戶已建立!現在可以登入了。" }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "登入成功" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "您可以關閉此視窗" }, "masterPassSent": { "message": "已寄出包含您主密碼提示的電子郵件。" @@ -522,13 +522,13 @@ "message": "無法在此頁面自動填入所選項目。請手動複製貼上。" }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "無法掃描此網頁的二維碼" }, "totpCaptureSuccess": { "message": "已新增驗證器金鑰。" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "從目前網頁掃描驗證器二維碼" }, "copyTOTP": { "message": "複製驗證器金鑰 (TOTP)" @@ -1061,7 +1061,7 @@ "message": "Turn off your browser’s built in password manager settings to avoid conflicts." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "編輯瀏覽器設定" }, "autofillOverlayVisibilityOff": { "message": "關閉", @@ -1500,7 +1500,7 @@ "message": "無效的 PIN 碼。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "輸入太多無效 PIN 碼。 登出。" }, "unlockWithBiometrics": { "message": "使用生物特徵辨識解鎖" @@ -2005,7 +2005,7 @@ "message": "選擇資料夾⋯" }, "noFoldersFound": { - "message": "No folders found", + "message": "未找到資料夾", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { @@ -2017,7 +2017,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { - "message": "Verification required", + "message": "需要驗證", "description": "Default title for the user verification dialog." }, "hours": { @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "拒絕存取。您沒有檢視此頁面的權限。" }, @@ -2558,7 +2552,7 @@ "description": "Notification message for when an import has failed." }, "importNetworkError": { - "message": "Network error encountered during import.", + "message": "匯入時遇到網路錯誤", "description": "Notification message for when an import has failed due to a network error." }, "aliasDomain": { @@ -2664,34 +2658,34 @@ "message": "Verification required for this action. Set a PIN to continue." }, "setPin": { - "message": "Set PIN" + "message": "設定 PIN 碼" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "生物辨識驗證" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "正在等待確認" }, "couldNotCompleteBiometrics": { "message": "Could not complete biometrics." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "需要不同的方法嗎?" }, "useMasterPassword": { - "message": "Use master password" + "message": "用主密碼" }, "usePin": { - "message": "Use PIN" + "message": "使用 PIN 碼" }, "useBiometrics": { - "message": "Use biometrics" + "message": "用生物識別" }, "enterVerificationCodeSentToEmail": { "message": "Enter the verification code that was sent to your email." }, "resendCode": { - "message": "Resend code" + "message": "重新傳送驗證碼" }, "total": { "message": "總計" @@ -2718,7 +2712,7 @@ "message": "Popout extension" }, "launchDuo": { - "message": "Launch Duo" + "message": "開啟Duo" }, "importFormatError": { "message": "資料格式不正確。請檢查您匯入的檔案後再試一次。" @@ -2852,13 +2846,13 @@ "message": "使用者名稱或密碼不正確" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "密碼錯誤" }, "incorrectCode": { - "message": "Incorrect code" + "message": "錯誤驗證碼" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN 碼錯誤" }, "multifactorAuthenticationFailed": { "message": "多因素驗證失敗" @@ -2919,7 +2913,7 @@ "message": "切換帳戶" }, "switchAccounts": { - "message": "Switch accounts" + "message": "切換帳號" }, "switchToAccount": { "message": "切換帳戶" @@ -2991,7 +2985,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": "設為預設", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { diff --git a/apps/browser/src/auth/background/service-factories/auth-service.factory.ts b/apps/browser/src/auth/background/service-factories/auth-service.factory.ts index d0a4d2db1b..f600efa18d 100644 --- a/apps/browser/src/auth/background/service-factories/auth-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/auth-service.factory.ts @@ -15,21 +15,26 @@ import { factory, } from "../../../platform/background/service-factories/factory-options"; import { - messagingServiceFactory, MessagingServiceInitOptions, + messagingServiceFactory, } from "../../../platform/background/service-factories/messaging-service.factory"; import { StateServiceInitOptions, stateServiceFactory, } from "../../../platform/background/service-factories/state-service.factory"; +import { AccountServiceInitOptions, accountServiceFactory } from "./account-service.factory"; +import { TokenServiceInitOptions, tokenServiceFactory } from "./token-service.factory"; + type AuthServiceFactoryOptions = FactoryOptions; export type AuthServiceInitOptions = AuthServiceFactoryOptions & + AccountServiceInitOptions & MessagingServiceInitOptions & CryptoServiceInitOptions & ApiServiceInitOptions & - StateServiceInitOptions; + StateServiceInitOptions & + TokenServiceInitOptions; export function authServiceFactory( cache: { authService?: AbstractAuthService } & CachedServices, @@ -41,10 +46,12 @@ export function authServiceFactory( opts, async () => new AuthService( + await accountServiceFactory(cache, opts), await messagingServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts), await apiServiceFactory(cache, opts), await stateServiceFactory(cache, opts), + await tokenServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/auth/background/service-factories/device-trust-crypto-service.factory.ts b/apps/browser/src/auth/background/service-factories/device-trust-crypto-service.factory.ts index 6b8d3c09e3..cac6f9bbe8 100644 --- a/apps/browser/src/auth/background/service-factories/device-trust-crypto-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/device-trust-crypto-service.factory.ts @@ -39,9 +39,18 @@ import { platformUtilsServiceFactory, } from "../../../platform/background/service-factories/platform-utils-service.factory"; import { - StateServiceInitOptions, - stateServiceFactory, -} from "../../../platform/background/service-factories/state-service.factory"; + StateProviderInitOptions, + stateProviderFactory, +} from "../../../platform/background/service-factories/state-provider.factory"; +import { + SecureStorageServiceInitOptions, + secureStorageServiceFactory, +} from "../../../platform/background/service-factories/storage-service.factory"; + +import { + UserDecryptionOptionsServiceInitOptions, + userDecryptionOptionsServiceFactory, +} from "./user-decryption-options-service.factory"; type DeviceTrustCryptoServiceFactoryOptions = FactoryOptions; @@ -50,11 +59,13 @@ export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactor CryptoFunctionServiceInitOptions & CryptoServiceInitOptions & EncryptServiceInitOptions & - StateServiceInitOptions & AppIdServiceInitOptions & DevicesApiServiceInitOptions & I18nServiceInitOptions & - PlatformUtilsServiceInitOptions; + PlatformUtilsServiceInitOptions & + StateProviderInitOptions & + SecureStorageServiceInitOptions & + UserDecryptionOptionsServiceInitOptions; export function deviceTrustCryptoServiceFactory( cache: { deviceTrustCryptoService?: DeviceTrustCryptoServiceAbstraction } & CachedServices, @@ -70,11 +81,13 @@ export function deviceTrustCryptoServiceFactory( await cryptoFunctionServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts), await encryptServiceFactory(cache, opts), - await stateServiceFactory(cache, opts), await appIdServiceFactory(cache, opts), await devicesApiServiceFactory(cache, opts), await i18nServiceFactory(cache, opts), await platformUtilsServiceFactory(cache, opts), + await stateProviderFactory(cache, opts), + await secureStorageServiceFactory(cache, opts), + await userDecryptionOptionsServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/auth/background/service-factories/key-connector-service.factory.ts b/apps/browser/src/auth/background/service-factories/key-connector-service.factory.ts index 5fd1866c83..4a0dd07b32 100644 --- a/apps/browser/src/auth/background/service-factories/key-connector-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/key-connector-service.factory.ts @@ -27,9 +27,9 @@ import { LogServiceInitOptions, } from "../../../platform/background/service-factories/log-service.factory"; import { - stateServiceFactory, - StateServiceInitOptions, -} from "../../../platform/background/service-factories/state-service.factory"; + stateProviderFactory, + StateProviderInitOptions, +} from "../../../platform/background/service-factories/state-provider.factory"; import { TokenServiceInitOptions, tokenServiceFactory } from "./token-service.factory"; @@ -40,13 +40,13 @@ type KeyConnectorServiceFactoryOptions = FactoryOptions & { }; export type KeyConnectorServiceInitOptions = KeyConnectorServiceFactoryOptions & - StateServiceInitOptions & CryptoServiceInitOptions & ApiServiceInitOptions & TokenServiceInitOptions & LogServiceInitOptions & OrganizationServiceInitOptions & - KeyGenerationServiceInitOptions; + KeyGenerationServiceInitOptions & + StateProviderInitOptions; export function keyConnectorServiceFactory( cache: { keyConnectorService?: AbstractKeyConnectorService } & CachedServices, @@ -58,7 +58,6 @@ export function keyConnectorServiceFactory( opts, async () => new KeyConnectorService( - await stateServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts), await apiServiceFactory(cache, opts), await tokenServiceFactory(cache, opts), @@ -66,6 +65,7 @@ export function keyConnectorServiceFactory( await organizationServiceFactory(cache, opts), await keyGenerationServiceFactory(cache, opts), opts.keyConnectorServiceOptions.logoutCallback, + await stateProviderFactory(cache, opts), ), ); } diff --git a/apps/browser/src/auth/background/service-factories/login-email-service.factory.ts b/apps/browser/src/auth/background/service-factories/login-email-service.factory.ts new file mode 100644 index 0000000000..6e98a9a886 --- /dev/null +++ b/apps/browser/src/auth/background/service-factories/login-email-service.factory.ts @@ -0,0 +1,28 @@ +import { LoginEmailServiceAbstraction, LoginEmailService } from "@bitwarden/auth/common"; + +import { + CachedServices, + factory, + FactoryOptions, +} from "../../../platform/background/service-factories/factory-options"; +import { + stateProviderFactory, + StateProviderInitOptions, +} from "../../../platform/background/service-factories/state-provider.factory"; + +type LoginEmailServiceFactoryOptions = FactoryOptions; + +export type LoginEmailServiceInitOptions = LoginEmailServiceFactoryOptions & + StateProviderInitOptions; + +export function loginEmailServiceFactory( + cache: { loginEmailService?: LoginEmailServiceAbstraction } & CachedServices, + opts: LoginEmailServiceInitOptions, +): Promise { + return factory( + cache, + "loginEmailService", + opts, + async () => new LoginEmailService(await stateProviderFactory(cache, opts)), + ); +} diff --git a/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts b/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts index b0ae87a75f..2cc4692ca9 100644 --- a/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts @@ -9,7 +9,10 @@ import { ApiServiceInitOptions, } from "../../../platform/background/service-factories/api-service.factory"; import { appIdServiceFactory } from "../../../platform/background/service-factories/app-id-service.factory"; -import { billingAccountProfileStateServiceFactory } from "../../../platform/background/service-factories/billing-account-profile-state-service.factory"; +import { + billingAccountProfileStateServiceFactory, + BillingAccountProfileStateServiceInitOptions, +} from "../../../platform/background/service-factories/billing-account-profile-state-service.factory"; import { CryptoServiceInitOptions, cryptoServiceFactory, @@ -70,6 +73,10 @@ import { } from "./key-connector-service.factory"; import { tokenServiceFactory, TokenServiceInitOptions } from "./token-service.factory"; import { twoFactorServiceFactory, TwoFactorServiceInitOptions } from "./two-factor-service.factory"; +import { + internalUserDecryptionOptionServiceFactory, + UserDecryptionOptionsServiceInitOptions, +} from "./user-decryption-options-service.factory"; type LoginStrategyServiceFactoryOptions = FactoryOptions; @@ -90,7 +97,9 @@ export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions PasswordStrengthServiceInitOptions & DeviceTrustCryptoServiceInitOptions & AuthRequestServiceInitOptions & - GlobalStateProviderInitOptions; + UserDecryptionOptionsServiceInitOptions & + GlobalStateProviderInitOptions & + BillingAccountProfileStateServiceInitOptions; export function loginStrategyServiceFactory( cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices, @@ -119,6 +128,7 @@ export function loginStrategyServiceFactory( await policyServiceFactory(cache, opts), await deviceTrustCryptoServiceFactory(cache, opts), await authRequestServiceFactory(cache, opts), + await internalUserDecryptionOptionServiceFactory(cache, opts), await globalStateProviderFactory(cache, opts), await billingAccountProfileStateServiceFactory(cache, opts), ), diff --git a/apps/browser/src/auth/background/service-factories/token-service.factory.ts b/apps/browser/src/auth/background/service-factories/token-service.factory.ts index 25c30460f0..ba42998209 100644 --- a/apps/browser/src/auth/background/service-factories/token-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/token-service.factory.ts @@ -1,6 +1,10 @@ import { TokenService as AbstractTokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; +import { + EncryptServiceInitOptions, + encryptServiceFactory, +} from "../../../platform/background/service-factories/encrypt-service.factory"; import { FactoryOptions, CachedServices, @@ -10,6 +14,14 @@ import { GlobalStateProviderInitOptions, globalStateProviderFactory, } from "../../../platform/background/service-factories/global-state-provider.factory"; +import { + KeyGenerationServiceInitOptions, + keyGenerationServiceFactory, +} from "../../../platform/background/service-factories/key-generation-service.factory"; +import { + LogServiceInitOptions, + logServiceFactory, +} from "../../../platform/background/service-factories/log-service.factory"; import { PlatformUtilsServiceInitOptions, platformUtilsServiceFactory, @@ -29,7 +41,10 @@ export type TokenServiceInitOptions = TokenServiceFactoryOptions & SingleUserStateProviderInitOptions & GlobalStateProviderInitOptions & PlatformUtilsServiceInitOptions & - SecureStorageServiceInitOptions; + SecureStorageServiceInitOptions & + KeyGenerationServiceInitOptions & + EncryptServiceInitOptions & + LogServiceInitOptions; export function tokenServiceFactory( cache: { tokenService?: AbstractTokenService } & CachedServices, @@ -45,6 +60,9 @@ export function tokenServiceFactory( await globalStateProviderFactory(cache, opts), (await platformUtilsServiceFactory(cache, opts)).supportsSecureStorage(), await secureStorageServiceFactory(cache, opts), + await keyGenerationServiceFactory(cache, opts), + await encryptServiceFactory(cache, opts), + await logServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/auth/background/service-factories/user-decryption-options-service.factory.ts b/apps/browser/src/auth/background/service-factories/user-decryption-options-service.factory.ts new file mode 100644 index 0000000000..549639a3c7 --- /dev/null +++ b/apps/browser/src/auth/background/service-factories/user-decryption-options-service.factory.ts @@ -0,0 +1,46 @@ +import { + InternalUserDecryptionOptionsServiceAbstraction, + UserDecryptionOptionsService, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; + +import { + CachedServices, + factory, + FactoryOptions, +} from "../../../platform/background/service-factories/factory-options"; +import { + stateProviderFactory, + StateProviderInitOptions, +} from "../../../platform/background/service-factories/state-provider.factory"; + +type UserDecryptionOptionsServiceFactoryOptions = FactoryOptions; + +export type UserDecryptionOptionsServiceInitOptions = UserDecryptionOptionsServiceFactoryOptions & + StateProviderInitOptions; + +export function userDecryptionOptionsServiceFactory( + cache: { + userDecryptionOptionsService?: InternalUserDecryptionOptionsServiceAbstraction; + } & CachedServices, + opts: UserDecryptionOptionsServiceInitOptions, +): Promise { + return factory( + cache, + "userDecryptionOptionsService", + opts, + async () => new UserDecryptionOptionsService(await stateProviderFactory(cache, opts)), + ); +} + +export async function internalUserDecryptionOptionServiceFactory( + cache: { + userDecryptionOptionsService?: InternalUserDecryptionOptionsServiceAbstraction; + } & CachedServices, + opts: UserDecryptionOptionsServiceInitOptions, +): Promise { + return (await userDecryptionOptionsServiceFactory( + cache, + opts, + )) as InternalUserDecryptionOptionsServiceAbstraction; +} diff --git a/apps/browser/src/auth/background/service-factories/user-verification-service.factory.ts b/apps/browser/src/auth/background/service-factories/user-verification-service.factory.ts index ff08ddf689..e8be9099ca 100644 --- a/apps/browser/src/auth/background/service-factories/user-verification-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/user-verification-service.factory.ts @@ -32,6 +32,10 @@ import { } from "../../../platform/background/service-factories/state-service.factory"; import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory"; +import { + userDecryptionOptionsServiceFactory, + UserDecryptionOptionsServiceInitOptions, +} from "./user-decryption-options-service.factory"; import { UserVerificationApiServiceInitOptions, userVerificationApiServiceFactory, @@ -44,6 +48,7 @@ export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryO CryptoServiceInitOptions & I18nServiceInitOptions & UserVerificationApiServiceInitOptions & + UserDecryptionOptionsServiceInitOptions & PinCryptoServiceInitOptions & LogServiceInitOptions & VaultTimeoutSettingsServiceInitOptions & @@ -63,6 +68,7 @@ export function userVerificationServiceFactory( await cryptoServiceFactory(cache, opts), await i18nServiceFactory(cache, opts), await userVerificationApiServiceFactory(cache, opts), + await userDecryptionOptionsServiceFactory(cache, opts), await pinCryptoServiceFactory(cache, opts), await logServiceFactory(cache, opts), await vaultTimeoutSettingsServiceFactory(cache, opts), diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts index cf78f2ff91..32ebee7c75 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts @@ -65,7 +65,7 @@ export class AccountSwitcherService { name: account.name ?? account.email, email: account.email, id: id, - server: await this.environmentService.getHost(id), + server: (await this.environmentService.getEnvironment(id))?.getHostname(), status: account.status, isActive: id === activeAccount?.id, avatarColor: await firstValueFrom( diff --git a/apps/browser/src/auth/popup/hint.component.ts b/apps/browser/src/auth/popup/hint.component.ts index a1f79cd457..214a43efb7 100644 --- a/apps/browser/src/auth/popup/hint.component.ts +++ b/apps/browser/src/auth/popup/hint.component.ts @@ -2,8 +2,8 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component"; +import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { LoginService } from "@bitwarden/common/auth/abstractions/login.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"; @@ -20,9 +20,9 @@ export class HintComponent extends BaseHintComponent { apiService: ApiService, logService: LogService, private route: ActivatedRoute, - loginService: LoginService, + loginEmailService: LoginEmailServiceAbstraction, ) { - super(router, i18nService, apiService, platformUtilsService, logService, loginService); + super(router, i18nService, apiService, platformUtilsService, logService, loginEmailService); super.onSuccessfulSubmit = async () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. diff --git a/apps/browser/src/auth/popup/home.component.html b/apps/browser/src/auth/popup/home.component.html index f70a4c6d03..8e23d96c49 100644 --- a/apps/browser/src/auth/popup/home.component.html +++ b/apps/browser/src/auth/popup/home.component.html @@ -30,7 +30,7 @@ diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts index b5598f13f7..db83736be8 100644 --- a/apps/browser/src/auth/popup/home.component.ts +++ b/apps/browser/src/auth/popup/home.component.ts @@ -1,14 +1,13 @@ import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; -import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; +import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { AccountSwitcherService } from "./account-switching/services/account-switcher.service"; @@ -29,38 +28,32 @@ export class HomeComponent implements OnInit, OnDestroy { constructor( protected platformUtilsService: PlatformUtilsService, - private stateService: StateService, private formBuilder: FormBuilder, private router: Router, private i18nService: I18nService, private environmentService: EnvironmentService, - private loginService: LoginService, + private loginEmailService: LoginEmailServiceAbstraction, private accountSwitcherService: AccountSwitcherService, ) {} async ngOnInit(): Promise { - let savedEmail = this.loginService.getEmail(); - const rememberEmail = this.loginService.getRememberEmail(); + const email = this.loginEmailService.getEmail(); + const rememberEmail = this.loginEmailService.getRememberEmail(); - if (savedEmail != null) { - this.formGroup.patchValue({ - email: savedEmail, - rememberEmail: rememberEmail, - }); + if (email != null) { + this.formGroup.patchValue({ email, rememberEmail }); } else { - savedEmail = await this.stateService.getRememberedEmail(); - if (savedEmail != null) { - this.formGroup.patchValue({ - email: savedEmail, - rememberEmail: true, - }); + const storedEmail = await firstValueFrom(this.loginEmailService.storedEmail$); + + if (storedEmail != null) { + this.formGroup.patchValue({ email: storedEmail, rememberEmail: true }); } } this.environmentSelector.onOpenSelfHostedSettings .pipe(takeUntil(this.destroyed$)) .subscribe(() => { - this.setFormValues(); + this.setLoginEmailValues(); // 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(["environment"]); @@ -76,8 +69,9 @@ export class HomeComponent implements OnInit, OnDestroy { return this.accountSwitcherService.availableAccounts$; } - submit() { + async submit() { this.formGroup.markAllAsTouched(); + if (this.formGroup.invalid) { this.platformUtilsService.showToast( "error", @@ -87,19 +81,12 @@ export class HomeComponent implements OnInit, OnDestroy { return; } - this.loginService.setEmail(this.formGroup.value.email); - this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); - // 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(["login"], { queryParams: { email: this.formGroup.value.email } }); + this.setLoginEmailValues(); + await this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } }); } - get selfHostedDomain() { - return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null; - } - - setFormValues() { - this.loginService.setEmail(this.formGroup.value.email); - this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); + setLoginEmailValues() { + this.loginEmailService.setEmail(this.formGroup.value.email); + this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail); } } diff --git a/apps/browser/src/auth/popup/lock.component.ts b/apps/browser/src/auth/popup/lock.component.ts index f2c56a23ae..f232eca45a 100644 --- a/apps/browser/src/auth/popup/lock.component.ts +++ b/apps/browser/src/auth/popup/lock.component.ts @@ -9,6 +9,7 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; 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"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; @@ -62,6 +63,7 @@ export class LockComponent extends BaseLockComponent { pinCryptoService: PinCryptoServiceAbstraction, private routerService: BrowserRouterService, biometricStateService: BiometricStateService, + accountService: AccountService, ) { super( router, @@ -84,6 +86,7 @@ export class LockComponent extends BaseLockComponent { userVerificationService, pinCryptoService, biometricStateService, + accountService, ); this.successRoute = "/tabs/current"; this.isInitialLockScreen = (window as any).previousPopupUrl == null; diff --git a/apps/browser/src/auth/popup/login-via-auth-request.component.ts b/apps/browser/src/auth/popup/login-via-auth-request.component.ts index a22636389a..52f311ce7b 100644 --- a/apps/browser/src/auth/popup/login-via-auth-request.component.ts +++ b/apps/browser/src/auth/popup/login-via-auth-request.component.ts @@ -1,17 +1,18 @@ import { Location } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component } from "@angular/core"; import { Router } from "@angular/router"; import { LoginViaAuthRequestComponent as BaseLoginWithDeviceComponent } from "@bitwarden/angular/auth/components/login-via-auth-request.component"; import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction, + LoginEmailServiceAbstraction, } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; -import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -28,10 +29,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv selector: "app-login-via-auth-request", templateUrl: "login-via-auth-request.component.html", }) -export class LoginViaAuthRequestComponent - extends BaseLoginWithDeviceComponent - implements OnInit, OnDestroy -{ +export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent { constructor( router: Router, cryptoService: CryptoService, @@ -47,11 +45,12 @@ export class LoginViaAuthRequestComponent anonymousHubService: AnonymousHubService, validationService: ValidationService, stateService: StateService, - loginService: LoginService, + loginEmailService: LoginEmailServiceAbstraction, syncService: SyncService, deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, authRequestService: AuthRequestServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction, + accountService: AccountService, private location: Location, ) { super( @@ -69,10 +68,11 @@ export class LoginViaAuthRequestComponent anonymousHubService, validationService, stateService, - loginService, + loginEmailService, deviceTrustCryptoService, authRequestService, loginStrategyService, + accountService, ); super.onSuccessfulLogin = async () => { await syncService.fullSync(true); diff --git a/apps/browser/src/auth/popup/login.component.html b/apps/browser/src/auth/popup/login.component.html index f6ebb747f7..b24a25a0f1 100644 --- a/apps/browser/src/auth/popup/login.component.html +++ b/apps/browser/src/auth/popup/login.component.html @@ -52,7 +52,7 @@ diff --git a/apps/browser/src/auth/popup/login.component.ts b/apps/browser/src/auth/popup/login.component.ts index c1dd952658..ff0ee8a392 100644 --- a/apps/browser/src/auth/popup/login.component.ts +++ b/apps/browser/src/auth/popup/login.component.ts @@ -1,12 +1,15 @@ import { Component, NgZone } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; +import { + LoginStrategyServiceAbstraction, + LoginEmailServiceAbstraction, +} from "@bitwarden/auth/common"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; 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"; @@ -45,7 +48,7 @@ export class LoginComponent extends BaseLoginComponent { formBuilder: FormBuilder, formValidationErrorService: FormValidationErrorsService, route: ActivatedRoute, - loginService: LoginService, + loginEmailService: LoginEmailServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, webAuthnLoginService: WebAuthnLoginServiceAbstraction, ) { @@ -65,7 +68,7 @@ export class LoginComponent extends BaseLoginComponent { formBuilder, formValidationErrorService, route, - loginService, + loginEmailService, ssoLoginService, webAuthnLoginService, ); @@ -76,8 +79,8 @@ export class LoginComponent extends BaseLoginComponent { this.showPasswordless = flagEnabled("showPasswordless"); if (this.showPasswordless) { - this.formGroup.controls.email.setValue(this.loginService.getEmail()); - this.formGroup.controls.rememberEmail.setValue(this.loginService.getRememberEmail()); + this.formGroup.controls.email.setValue(this.loginEmailService.getEmail()); + this.formGroup.controls.rememberEmail.setValue(this.loginEmailService.getRememberEmail()); // 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.validateEmail(); @@ -93,7 +96,7 @@ export class LoginComponent extends BaseLoginComponent { async launchSsoBrowser() { // Save off email for SSO await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); - await this.loginService.saveEmailSettings(); + await this.loginEmailService.saveEmailSettings(); // Generate necessary sso params const passwordOptions: any = { type: "password", @@ -114,7 +117,8 @@ export class LoginComponent extends BaseLoginComponent { await this.ssoLoginService.setCodeVerifier(codeVerifier); await this.ssoLoginService.setSsoState(state); - let url = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + let url = env.getWebVaultUrl(); if (url == null) { url = "https://vault.bitwarden.com"; } diff --git a/apps/browser/src/auth/popup/services/unauth-guard.service.ts b/apps/browser/src/auth/popup/services/unauth-guard.service.ts index 062239a7d3..0fbb4ac9ba 100644 --- a/apps/browser/src/auth/popup/services/unauth-guard.service.ts +++ b/apps/browser/src/auth/popup/services/unauth-guard.service.ts @@ -1,8 +1,5 @@ -import { Injectable } from "@angular/core"; - import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards"; -@Injectable() export class UnauthGuardService extends BaseUnauthGuardService { protected homepage = "tabs/current"; } diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts index ac98966b4a..ea1cacc7ac 100644 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ b/apps/browser/src/auth/popup/set-password.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; +import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; @@ -37,6 +38,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent { route: ActivatedRoute, organizationApiService: OrganizationApiServiceAbstraction, organizationUserService: OrganizationUserService, + userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, dialogService: DialogService, ) { @@ -55,6 +57,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent { stateService, organizationApiService, organizationUserService, + userDecryptionOptionsService, ssoLoginService, dialogService, ); diff --git a/apps/browser/src/auth/popup/sso.component.ts b/apps/browser/src/auth/popup/sso.component.ts index 19d7977819..228c7401fd 100644 --- a/apps/browser/src/auth/popup/sso.component.ts +++ b/apps/browser/src/auth/popup/sso.component.ts @@ -1,14 +1,18 @@ import { Component, Inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; +import { + LoginStrategyServiceAbstraction, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +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"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -39,7 +43,8 @@ export class SsoComponent extends BaseSsoComponent { syncService: SyncService, environmentService: EnvironmentService, logService: LogService, - configService: ConfigServiceAbstraction, + userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + configService: ConfigService, protected authService: AuthService, @Inject(WINDOW) private win: Window, ) { @@ -56,12 +61,13 @@ export class SsoComponent extends BaseSsoComponent { environmentService, passwordGenerationService, logService, + userDecryptionOptionsService, configService, ); - const url = this.environmentService.getWebVaultUrl(); - - this.redirectUri = url + "/sso-connector.html"; + environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { + this.redirectUri = env.getWebVaultUrl() + "/sso-connector.html"; + }); this.clientId = "browser"; super.onSuccessfulLogin = async () => { diff --git a/apps/browser/src/auth/popup/two-factor.component.ts b/apps/browser/src/auth/popup/two-factor.component.ts index e511122f9a..dd541f63f8 100644 --- a/apps/browser/src/auth/popup/two-factor.component.ts +++ b/apps/browser/src/auth/popup/two-factor.component.ts @@ -1,19 +1,22 @@ import { Component, Inject } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, Subscription } from "rxjs"; +import { Subject, Subscription, firstValueFrom } from "rxjs"; import { filter, first, takeUntil } from "rxjs/operators"; import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; +import { + LoginStrategyServiceAbstraction, + LoginEmailServiceAbstraction, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +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"; @@ -54,8 +57,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { logService: LogService, twoFactorService: TwoFactorService, appIdService: AppIdService, - loginService: LoginService, - configService: ConfigServiceAbstraction, + loginEmailService: LoginEmailServiceAbstraction, + userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + configService: ConfigService, ssoLoginService: SsoLoginServiceAbstraction, private dialogService: DialogService, @Inject(WINDOW) protected win: Window, @@ -74,7 +78,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { logService, twoFactorService, appIdService, - loginService, + loginEmailService, + userDecryptionOptionsService, ssoLoginService, configService, ); @@ -220,7 +225,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { } } - override launchDuoFrameless() { + override async launchDuoFrameless() { const duoHandOffMessage = { title: this.i18nService.t("youSuccessfullyLoggedIn"), message: this.i18nService.t("youMayCloseThisWindow"), @@ -229,8 +234,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { // we're using the connector here as a way to set a cookie with translations // before continuing to the duo frameless url + const env = await firstValueFrom(this.environmentService.environment$); const launchUrl = - this.environmentService.getWebVaultUrl() + + env.getWebVaultUrl() + "/duo-redirect-connector.html" + "?duoFramelessUrl=" + encodeURIComponent(this.duoFramelessUrl) + diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index ab704a1d37..ac40bb315b 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -113,7 +113,7 @@ type NotificationBackgroundExtensionMessageHandlers = { bgGetEnableChangedPasswordPrompt: () => Promise; bgGetEnableAddedLoginPrompt: () => Promise; bgGetExcludedDomains: () => Promise; - getWebVaultUrlForNotification: () => string; + getWebVaultUrlForNotification: () => Promise; }; export { diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 0674958d65..e242e0961b 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -1,13 +1,14 @@ import { mock } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom } 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 { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; @@ -1348,16 +1349,21 @@ describe("NotificationBackground", () => { const message: NotificationBackgroundExtensionMessage = { command: "getWebVaultUrlForNotification", }; - const webVaultUrl = "https://example.com"; + const env = new SelfHostedEnvironment({ webVault: "https://example.com" }); + + Object.defineProperty(environmentService, "environment$", { + configurable: true, + get: () => null, + }); + const environmentServiceSpy = jest - .spyOn(environmentService, "getWebVaultUrl") - .mockReturnValueOnce(webVaultUrl); + .spyOn(environmentService as any, "environment$", "get") + .mockReturnValue(new BehaviorSubject(env).asObservable()); sendExtensionRuntimeMessage(message); await flushPromises(); expect(environmentServiceSpy).toHaveBeenCalled(); - expect(environmentServiceSpy).toHaveReturnedWith(webVaultUrl); }); }); }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index bdf2c9b8ba..1fe3dd4f60 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -165,6 +165,7 @@ export default class NotificationBackground { notificationQueueMessage: NotificationQueueMessageItem, ) { const notificationType = notificationQueueMessage.type; + const typeData: Record = { isVaultLocked: notificationQueueMessage.wasVaultLocked, theme: await firstValueFrom(this.themeStateService.selectedTheme$), @@ -655,8 +656,9 @@ export default class NotificationBackground { return await firstValueFrom(this.folderService.folderViews$); } - private getWebVaultUrl(): string { - return this.environmentService.getWebVaultUrl(); + private async getWebVaultUrl(): Promise { + const env = await firstValueFrom(this.environmentService.environment$); + return env.getWebVaultUrl(); } private async removeIndividualVault(): Promise { diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index d7156f4272..4a80fd0fa8 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, mockReset } from "jest-mock-extended"; -import { of } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; @@ -12,9 +12,13 @@ import { DefaultDomainSettingsService, DomainSettingsService, } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { + EnvironmentService, + Region, +} from "@bitwarden/common/platform/abstractions/environment.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { CloudEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { I18nService } from "@bitwarden/common/platform/services/i18n.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { @@ -48,8 +52,6 @@ import { import OverlayBackground from "./overlay.background"; -const iconServerUrl = "https://icons.bitwarden.com/"; - describe("OverlayBackground", () => { const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -61,9 +63,15 @@ describe("OverlayBackground", () => { const cipherService = mock(); const autofillService = mock(); const authService = mock(); - const environmentService = mock({ - getIconsUrl: () => iconServerUrl, - }); + + const environmentService = mock(); + environmentService.environment$ = new BehaviorSubject( + new CloudEnvironment({ + key: Region.US, + domain: "bitwarden.com", + urls: { icons: "https://icons.bitwarden.com/" }, + }), + ); const stateService = mock(); const autofillSettingsService = mock(); const i18nService = mock(); @@ -117,7 +125,8 @@ describe("OverlayBackground", () => { describe("removePageDetails", () => { it("removes the page details for a specific tab from the pageDetailsForTab object", () => { const tabId = 1; - overlayBackground["pageDetailsForTab"][tabId] = [createPageDetailMock()]; + const frameId = 2; + overlayBackground["pageDetailsForTab"][tabId] = new Map([[frameId, createPageDetailMock()]]); overlayBackground.removePageDetails(tabId); expect(overlayBackground["pageDetailsForTab"][tabId]).toBeUndefined(); @@ -856,29 +865,40 @@ describe("OverlayBackground", () => { sender, ); - expect(overlayBackground["pageDetailsForTab"][sender.tab.id]).toStrictEqual([ - { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }, - ]); + expect(overlayBackground["pageDetailsForTab"][sender.tab.id]).toStrictEqual( + new Map([ + [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }], + ]), + ); }); it("updates the page details for a tab that already has a set of page details stored ", () => { - overlayBackground["pageDetailsForTab"][sender.tab.id] = [ - { - frameId: sender.frameId, - tab: sender.tab, - details: pageDetails1, - }, - ]; + const secondFrameSender = mock({ + tab: { id: 1 }, + frameId: 3, + }); + overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ + [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }], + ]); sendExtensionRuntimeMessage( { command: "collectPageDetailsResponse", details: pageDetails2 }, - sender, + secondFrameSender, ); - expect(overlayBackground["pageDetailsForTab"][sender.tab.id]).toStrictEqual([ - { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }, - { frameId: sender.frameId, tab: sender.tab, details: pageDetails2 }, - ]); + expect(overlayBackground["pageDetailsForTab"][sender.tab.id]).toStrictEqual( + new Map([ + [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }], + [ + secondFrameSender.frameId, + { + frameId: secondFrameSender.frameId, + tab: secondFrameSender.tab, + details: pageDetails2, + }, + ], + ]), + ); }); }); @@ -1188,6 +1208,10 @@ describe("OverlayBackground", () => { let getLoginCiphersSpy: jest.SpyInstance; let isPasswordRepromptRequiredSpy: jest.SpyInstance; let doAutoFillSpy: jest.SpyInstance; + let sender: chrome.runtime.MessageSender; + const pageDetails = createAutofillPageDetailsMock({ + login: { username: "username1", password: "password1" }, + }); beforeEach(() => { getLoginCiphersSpy = jest.spyOn(overlayBackground["overlayLoginCiphers"], "get"); @@ -1196,6 +1220,7 @@ describe("OverlayBackground", () => { "isPasswordRepromptRequired", ); doAutoFillSpy = jest.spyOn(overlayBackground["autofillService"], "doAutoFill"); + sender = mock({ tab: { id: 1 } }); }); it("ignores the fill request if the overlay cipher id is not provided", async () => { @@ -1207,12 +1232,27 @@ describe("OverlayBackground", () => { expect(doAutoFillSpy).not.toHaveBeenCalled(); }); + it("ignores the fill request if the tab does not contain any identified page details", async () => { + sendPortMessage(listPortSpy, { + command: "fillSelectedListItem", + overlayCipherId: "overlay-cipher-1", + }); + await flushPromises(); + + expect(getLoginCiphersSpy).not.toHaveBeenCalled(); + expect(isPasswordRepromptRequiredSpy).not.toHaveBeenCalled(); + expect(doAutoFillSpy).not.toHaveBeenCalled(); + }); + it("ignores the fill request if a master password reprompt is required", async () => { const cipher = mock({ reprompt: CipherRepromptType.Password, type: CipherType.Login, }); overlayBackground["overlayLoginCiphers"] = new Map([["overlay-cipher-1", cipher]]); + overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ + [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails }], + ]); getLoginCiphersSpy = jest.spyOn(overlayBackground["overlayLoginCiphers"], "get"); isPasswordRepromptRequiredSpy.mockResolvedValue(true); @@ -1239,6 +1279,14 @@ describe("OverlayBackground", () => { ["overlay-cipher-2", cipher2], ["overlay-cipher-3", cipher3], ]); + const pageDetailsForTab = { + frameId: sender.frameId, + tab: sender.tab, + details: pageDetails, + }; + overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ + [sender.frameId, pageDetailsForTab], + ]); isPasswordRepromptRequiredSpy.mockResolvedValue(false); sendPortMessage(listPortSpy, { @@ -1254,7 +1302,7 @@ describe("OverlayBackground", () => { expect(doAutoFillSpy).toHaveBeenCalledWith({ tab: listPortSpy.sender.tab, cipher: cipher2, - pageDetails: undefined, + pageDetails: [pageDetailsForTab], fillNewPassword: true, allowTotpAutofill: true, }); @@ -1270,6 +1318,9 @@ describe("OverlayBackground", () => { it("copies the cipher's totp code to the clipboard after filling", async () => { const cipher1 = mock({ id: "overlay-cipher-1" }); overlayBackground["overlayLoginCiphers"] = new Map([["overlay-cipher-1", cipher1]]); + overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ + [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails }], + ]); isPasswordRepromptRequiredSpy.mockResolvedValue(false); const copyToClipboardSpy = jest .spyOn(overlayBackground["platformUtilsService"], "copyToClipboard") diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 297fee3592..d8b3df9fb2 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -47,13 +47,16 @@ class OverlayBackground implements OverlayBackgroundInterface { private readonly openViewVaultItemPopout = openViewVaultItemPopout; private readonly openAddEditVaultItemPopout = openAddEditVaultItemPopout; private overlayLoginCiphers: Map = new Map(); - private pageDetailsForTab: Record = {}; + private pageDetailsForTab: Record< + chrome.runtime.MessageSender["tab"]["id"], + Map + > = {}; private userAuthStatus: AuthenticationStatus = AuthenticationStatus.LoggedOut; private overlayButtonPort: chrome.runtime.Port; private overlayListPort: chrome.runtime.Port; private focusedFieldData: FocusedFieldData; private overlayPageTranslations: Record; - private readonly iconsServerUrl: string; + private iconsServerUrl: string; private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = { openAutofillOverlay: () => this.openOverlay(false), autofillOverlayElementClosed: ({ message }) => this.overlayElementClosed(message), @@ -98,9 +101,7 @@ class OverlayBackground implements OverlayBackgroundInterface { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private themeStateService: ThemeStateService, - ) { - this.iconsServerUrl = this.environmentService.getIconsUrl(); - } + ) {} /** * Removes cached page details for a tab @@ -109,6 +110,11 @@ class OverlayBackground implements OverlayBackgroundInterface { * @param tabId - Used to reference the page details of a specific tab */ removePageDetails(tabId: number) { + if (!this.pageDetailsForTab[tabId]) { + return; + } + + this.pageDetailsForTab[tabId].clear(); delete this.pageDetailsForTab[tabId]; } @@ -118,6 +124,8 @@ class OverlayBackground implements OverlayBackgroundInterface { */ async init() { this.setupExtensionMessageListeners(); + const env = await firstValueFrom(this.environmentService.environment$); + this.iconsServerUrl = env.getIconsUrl(); await this.getOverlayVisibility(); await this.getAuthStatus(); } @@ -203,12 +211,13 @@ class OverlayBackground implements OverlayBackgroundInterface { details: message.details, }; - if (this.pageDetailsForTab[sender.tab.id]?.length) { - this.pageDetailsForTab[sender.tab.id].push(pageDetails); + const pageDetailsMap = this.pageDetailsForTab[sender.tab.id]; + if (!pageDetailsMap) { + this.pageDetailsForTab[sender.tab.id] = new Map([[sender.frameId, pageDetails]]); return; } - this.pageDetailsForTab[sender.tab.id] = [pageDetails]; + pageDetailsMap.set(sender.frameId, pageDetails); } /** @@ -222,7 +231,8 @@ class OverlayBackground implements OverlayBackgroundInterface { { overlayCipherId }: OverlayPortMessage, { sender }: chrome.runtime.Port, ) { - if (!overlayCipherId) { + const pageDetails = this.pageDetailsForTab[sender.tab.id]; + if (!overlayCipherId || !pageDetails?.size) { return; } @@ -234,7 +244,7 @@ class OverlayBackground implements OverlayBackgroundInterface { const totpCode = await this.autofillService.doAutoFill({ tab: sender.tab, cipher: cipher, - pageDetails: this.pageDetailsForTab[sender.tab.id], + pageDetails: Array.from(pageDetails.values()), fillNewPassword: true, allowTotpAutofill: true, }); diff --git a/apps/browser/src/autofill/background/web-request.background.ts b/apps/browser/src/autofill/background/web-request.background.ts index f4422e6d7f..8cdfa0f027 100644 --- a/apps/browser/src/autofill/background/web-request.background.ts +++ b/apps/browser/src/autofill/background/web-request.background.ts @@ -17,7 +17,7 @@ export default class WebRequestBackground { private authService: AuthService, ) { if (BrowserApi.isManifestVersion(2)) { - this.webRequest = (window as any).chrome.webRequest; + this.webRequest = chrome.webRequest; } this.isFirefox = platformUtilsService.isFirefox(); } diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts index 760b833044..596d6b7235 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts @@ -30,6 +30,7 @@ import { authServiceFactory, AuthServiceInitOptions, } from "../../auth/background/service-factories/auth-service.factory"; +import { KeyConnectorServiceInitOptions } from "../../auth/background/service-factories/key-connector-service.factory"; import { userVerificationServiceFactory } from "../../auth/background/service-factories/user-verification-service.factory"; import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window"; import { autofillSettingsServiceFactory } from "../../autofill/background/service_factories/autofill-settings-service.factory"; @@ -78,7 +79,9 @@ export class ContextMenuClickedHandler { static async mv3Create(cachedServices: CachedServices) { const stateFactory = new StateFactory(GlobalState, Account); - const serviceOptions: AuthServiceInitOptions & CipherServiceInitOptions = { + const serviceOptions: AuthServiceInitOptions & + CipherServiceInitOptions & + KeyConnectorServiceInitOptions = { apiServiceOptions: { logoutCallback: NOT_IMPLEMENTED, }, diff --git a/apps/browser/src/autofill/content/autofill-init.spec.ts b/apps/browser/src/autofill/content/autofill-init.spec.ts index 8912a8c0ba..b299ddccbf 100644 --- a/apps/browser/src/autofill/content/autofill-init.spec.ts +++ b/apps/browser/src/autofill/content/autofill-init.spec.ts @@ -24,6 +24,7 @@ describe("AutofillInit", () => { }, }); autofillInit = new AutofillInit(autofillOverlayContentService); + window.IntersectionObserver = jest.fn(() => mock()); }); afterEach(() => { diff --git a/apps/browser/src/autofill/content/autofiller.ts b/apps/browser/src/autofill/content/autofiller.ts index 5f43023d8b..0ca9d37187 100644 --- a/apps/browser/src/autofill/content/autofiller.ts +++ b/apps/browser/src/autofill/content/autofiller.ts @@ -10,7 +10,7 @@ function loadAutofiller() { let pageHref: string = null; let filledThisHref = false; let delayFillTimeout: number; - let doFillInterval: NodeJS.Timeout; + let doFillInterval: number | NodeJS.Timeout; const handleExtensionDisconnect = () => { clearDoFillInterval(); clearDelayFillTimeout(); diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 5d28bf8397..a730ee1eba 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -139,7 +139,7 @@ function initNotificationBar(message: NotificationBarWindowMessage) { }); }); - window.addEventListener("resize", adjustHeight); + globalThis.addEventListener("resize", adjustHeight); adjustHeight(); } @@ -384,7 +384,7 @@ function setupLogoLink(i18n: Record) { function setNotificationBarTheme() { let theme = notificationBarIframeInitData.theme; if (theme === ThemeType.System) { - theme = window.matchMedia("(prefers-color-scheme: dark)").matches + theme = globalThis.matchMedia("(prefers-color-scheme: dark)").matches ? ThemeType.Dark : ThemeType.Light; } @@ -393,5 +393,5 @@ function setNotificationBarTheme() { } function postMessageToParent(message: NotificationBarWindowMessage) { - window.parent.postMessage(message, windowMessageOrigin || "*"); + globalThis.parent.postMessage(message, windowMessageOrigin || "*"); } diff --git a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts index 0ec7db131c..b7a6f2a39e 100644 --- a/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts +++ b/apps/browser/src/autofill/overlay/iframe-content/autofill-overlay-iframe.service.ts @@ -211,7 +211,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf let borderColor: string; let verifiedTheme = theme; if (verifiedTheme === ThemeType.System) { - verifiedTheme = window.matchMedia("(prefers-color-scheme: dark)").matches + verifiedTheme = globalThis.matchMedia("(prefers-color-scheme: dark)").matches ? ThemeType.Dark : ThemeType.Light; } diff --git a/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts b/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts index 305a230e5c..8d4fa724af 100644 --- a/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts +++ b/apps/browser/src/autofill/overlay/pages/list/autofill-overlay-list.ts @@ -19,7 +19,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement { private ciphers: OverlayCipherData[] = []; private ciphersList: HTMLUListElement; private cipherListScrollIsDebounced = false; - private cipherListScrollDebounceTimeout: NodeJS.Timeout; + private cipherListScrollDebounceTimeout: number | NodeJS.Timeout; private currentCipherIndex = 0; private readonly showCiphersPerPage = 6; private readonly overlayListWindowMessageHandlers: OverlayListWindowMessageHandlers = { diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 77a5f982fd..54a91a5176 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -15,7 +15,7 @@ export interface PageDetail { export interface AutoFillOptions { cipher: CipherView; pageDetails: PageDetail[]; - doc?: typeof window.document; + doc?: typeof self.document; tab: chrome.tabs.Tab; skipUsernameOnlyFill?: boolean; onlyEmptyFields?: boolean; diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index 9f3ffea142..96a1b4c851 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts @@ -173,12 +173,10 @@ describe("AutofillOverlayContentService", () => { autofillFieldData = mock(); }); - it("ignores fields that are readonly", () => { + it("ignores fields that are readonly", async () => { autofillFieldData.readonly = 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 - autofillOverlayContentService.setupAutofillOverlayListenerOnField( + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( autofillFieldElement, autofillFieldData, ); @@ -186,12 +184,10 @@ describe("AutofillOverlayContentService", () => { expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled(); }); - it("ignores fields that contain a disabled attribute", () => { + it("ignores fields that contain a disabled attribute", async () => { autofillFieldData.disabled = 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 - autofillOverlayContentService.setupAutofillOverlayListenerOnField( + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( autofillFieldElement, autofillFieldData, ); @@ -199,12 +195,10 @@ describe("AutofillOverlayContentService", () => { expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled(); }); - it("ignores fields that are not viewable", () => { + it("ignores fields that are not viewable", async () => { autofillFieldData.viewable = 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 - autofillOverlayContentService.setupAutofillOverlayListenerOnField( + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( autofillFieldElement, autofillFieldData, ); @@ -213,12 +207,10 @@ describe("AutofillOverlayContentService", () => { }); it("ignores fields that are part of the ExcludedOverlayTypes", () => { - AutoFillConstants.ExcludedOverlayTypes.forEach((excludedType) => { + AutoFillConstants.ExcludedOverlayTypes.forEach(async (excludedType) => { autofillFieldData.type = excludedType; - // 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 - autofillOverlayContentService.setupAutofillOverlayListenerOnField( + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( autofillFieldElement, autofillFieldData, ); @@ -227,12 +219,10 @@ describe("AutofillOverlayContentService", () => { }); }); - it("ignores fields that contain the keyword `search`", () => { + it("ignores fields that contain the keyword `search`", async () => { autofillFieldData.placeholder = "search"; - // 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 - autofillOverlayContentService.setupAutofillOverlayListenerOnField( + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( autofillFieldElement, autofillFieldData, ); @@ -240,12 +230,10 @@ describe("AutofillOverlayContentService", () => { expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled(); }); - it("ignores fields that contain the keyword `captcha` ", () => { + it("ignores fields that contain the keyword `captcha` ", async () => { autofillFieldData.placeholder = "captcha"; - // 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 - autofillOverlayContentService.setupAutofillOverlayListenerOnField( + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( autofillFieldElement, autofillFieldData, ); @@ -253,12 +241,10 @@ describe("AutofillOverlayContentService", () => { expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled(); }); - it("ignores fields that do not appear as a login field", () => { + it("ignores fields that do not appear as a login field", async () => { autofillFieldData.placeholder = "not-a-login-field"; - // 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 - autofillOverlayContentService.setupAutofillOverlayListenerOnField( + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( autofillFieldElement, autofillFieldData, ); @@ -267,6 +253,17 @@ describe("AutofillOverlayContentService", () => { }); }); + it("skips setup on fields that have been previously set up", async () => { + autofillOverlayContentService["formFieldElements"].add(autofillFieldElement); + + await autofillOverlayContentService.setupAutofillOverlayListenerOnField( + autofillFieldElement, + autofillFieldData, + ); + + expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled(); + }); + describe("identifies the overlay visibility setting", () => { it("defaults the overlay visibility setting to `OnFieldFocus` if a value is not set", async () => { sendExtensionMessageSpy.mockResolvedValueOnce(undefined); 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 79abdc3938..4b786e6ca3 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -86,7 +86,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte formFieldElement: ElementWithOpId, autofillFieldData: AutofillField, ) { - if (this.isIgnoredField(autofillFieldData)) { + if (this.isIgnoredField(autofillFieldData) || this.formFieldElements.has(formFieldElement)) { return; } @@ -712,7 +712,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte private async getBoundingClientRectFromIntersectionObserver( formFieldElement: ElementWithOpId, ): Promise { - if (!("IntersectionObserver" in window) && !("IntersectionObserverEntry" in window)) { + if (!("IntersectionObserver" in globalThis) && !("IntersectionObserverEntry" in globalThis)) { return null; } @@ -901,7 +901,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte if ( this.focusedFieldData.focusedFieldRects?.top > 0 && - this.focusedFieldData.focusedFieldRects?.top < window.innerHeight + this.focusedFieldData.focusedFieldRects?.top < globalThis.innerHeight ) { return; } diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index e353a34ea0..dae29e61e4 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -42,7 +42,7 @@ import { export default class AutofillService implements AutofillServiceInterface { private openVaultItemPasswordRepromptPopout = openVaultItemPasswordRepromptPopout; - private openPasswordRepromptPopoutDebounce: NodeJS.Timeout; + private openPasswordRepromptPopoutDebounce: number | NodeJS.Timeout; private currentlyOpeningPasswordRepromptPopout = false; private autofillScriptPortsSet = new Set(); static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames); 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 d5c461269b..79cb41b9a1 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 @@ -27,6 +27,7 @@ describe("CollectAutofillContentService", () => { const domElementVisibilityService = new DomElementVisibilityService(); const autofillOverlayContentService = new AutofillOverlayContentService(); let collectAutofillContentService: CollectAutofillContentService; + const mockIntersectionObserver = mock(); beforeEach(() => { document.body.innerHTML = mockLoginForm; @@ -34,6 +35,7 @@ describe("CollectAutofillContentService", () => { domElementVisibilityService, autofillOverlayContentService, ); + window.IntersectionObserver = jest.fn(() => mockIntersectionObserver); }); afterEach(() => { @@ -2527,10 +2529,10 @@ describe("CollectAutofillContentService", () => { }); updatedAttributes.forEach((attribute) => { - it(`will update the ${attribute} value for the field element`, async () => { + it(`will update the ${attribute} value for the field element`, () => { jest.spyOn(collectAutofillContentService["autofillFieldElements"], "set"); - await collectAutofillContentService["updateAutofillFieldElementData"]( + collectAutofillContentService["updateAutofillFieldElementData"]( attribute, fieldElement, autofillField, @@ -2543,10 +2545,10 @@ describe("CollectAutofillContentService", () => { }); }); - it("will not update an attribute value if it is not present in the updateActions object", async () => { + it("will not update an attribute value if it is not present in the updateActions object", () => { jest.spyOn(collectAutofillContentService["autofillFieldElements"], "set"); - await collectAutofillContentService["updateAutofillFieldElementData"]( + collectAutofillContentService["updateAutofillFieldElementData"]( "random-attribute", fieldElement, autofillField, @@ -2555,4 +2557,67 @@ describe("CollectAutofillContentService", () => { expect(collectAutofillContentService["autofillFieldElements"].set).not.toBeCalled(); }); }); + + describe("handleFormElementIntersection", () => { + let isFormFieldViewableSpy: jest.SpyInstance; + let setupAutofillOverlayListenerOnFieldSpy: jest.SpyInstance; + + beforeEach(() => { + isFormFieldViewableSpy = jest.spyOn( + collectAutofillContentService["domElementVisibilityService"], + "isFormFieldViewable", + ); + setupAutofillOverlayListenerOnFieldSpy = jest.spyOn( + collectAutofillContentService["autofillOverlayContentService"], + "setupAutofillOverlayListenerOnField", + ); + }); + + it("skips the initial intersection event for an observed element", async () => { + const formFieldElement = document.createElement("input") as ElementWithOpId; + collectAutofillContentService["elementInitializingIntersectionObserver"].add( + formFieldElement, + ); + const entries = [ + { target: formFieldElement, isIntersecting: true }, + ] as unknown as IntersectionObserverEntry[]; + + await collectAutofillContentService["handleFormElementIntersection"](entries); + + expect(isFormFieldViewableSpy).not.toHaveBeenCalled(); + expect(setupAutofillOverlayListenerOnFieldSpy).not.toHaveBeenCalled(); + }); + + it("skips setting up the overlay listeners on a field that is not viewable", async () => { + const formFieldElement = document.createElement("input") as ElementWithOpId; + const entries = [ + { target: formFieldElement, isIntersecting: true }, + ] as unknown as IntersectionObserverEntry[]; + isFormFieldViewableSpy.mockReturnValueOnce(false); + + await collectAutofillContentService["handleFormElementIntersection"](entries); + + expect(isFormFieldViewableSpy).toHaveBeenCalledWith(formFieldElement); + expect(setupAutofillOverlayListenerOnFieldSpy).not.toHaveBeenCalled(); + }); + + it("sets up the overlay listeners on a viewable field", async () => { + const formFieldElement = document.createElement("input") as ElementWithOpId; + const autofillField = mock(); + const entries = [ + { target: formFieldElement, isIntersecting: true }, + ] as unknown as IntersectionObserverEntry[]; + isFormFieldViewableSpy.mockReturnValueOnce(true); + collectAutofillContentService["autofillFieldElements"].set(formFieldElement, autofillField); + collectAutofillContentService["intersectionObserver"] = mockIntersectionObserver; + + await collectAutofillContentService["handleFormElementIntersection"](entries); + + expect(isFormFieldViewableSpy).toHaveBeenCalledWith(formFieldElement); + expect(setupAutofillOverlayListenerOnFieldSpy).toHaveBeenCalledWith( + formFieldElement, + autofillField, + ); + }); + }); }); 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 1de801a2c2..63dee7f3b1 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -38,8 +38,10 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte private autofillFormElements: AutofillFormElements = new Map(); private autofillFieldElements: AutofillFieldElements = new Map(); private currentLocationHref = ""; + private intersectionObserver: IntersectionObserver; + private elementInitializingIntersectionObserver: Set = new Set(); private mutationObserver: MutationObserver; - private updateAutofillElementsAfterMutationTimeout: NodeJS.Timeout; + private updateAutofillElementsAfterMutationTimeout: number | NodeJS.Timeout; private readonly updateAfterMutationTimeoutDelay = 1000; private readonly ignoredInputTypes = new Set([ "hidden", @@ -70,6 +72,10 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte this.setupMutationObserver(); } + if (!this.intersectionObserver) { + this.setupIntersectionObserver(); + } + if (!this.domRecentlyMutated && this.noFieldsFound) { return this.getFormattedPageDetails({}, []); } @@ -180,7 +186,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte ): AutofillPageDetails { return { title: document.title, - url: (document.defaultView || window).location.href, + url: (document.defaultView || globalThis).location.href, documentUrl: document.location.href, forms: autofillFormsData, fields: autofillFieldsData, @@ -240,7 +246,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte * @private */ private getFormActionAttribute(element: ElementWithOpId): string { - return new URL(this.getPropertyOrAttribute(element, "action"), window.location.href).href; + return new URL(this.getPropertyOrAttribute(element, "action"), globalThis.location.href).href; } /** @@ -360,11 +366,14 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte tagName: this.getAttributeLowerCase(element, "tagName"), }; + if (!autofillFieldBase.viewable) { + this.elementInitializingIntersectionObserver.add(element); + this.intersectionObserver.observe(element); + } + if (elementIsSpanElement(element)) { this.cacheAutofillFieldElement(index, element, autofillFieldBase); - // 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.autofillOverlayContentService?.setupAutofillOverlayListenerOnField( + void this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField( element, autofillFieldBase, ); @@ -407,9 +416,10 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte }; this.cacheAutofillFieldElement(index, element, autofillField); - // 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.autofillOverlayContentService?.setupAutofillOverlayListenerOnField(element, autofillField); + void this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField( + element, + autofillField, + ); return autofillField; }; @@ -1189,8 +1199,6 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte 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.updateAutofillFieldElementData( attributeName, targetElement as ElementWithOpId, @@ -1232,13 +1240,12 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte /** * Updates the autofill field element data based on the passed attribute name. + * * @param {string} attributeName * @param {ElementWithOpId} element * @param {AutofillField} dataTarget - * @returns {Promise} - * @private */ - private async updateAutofillFieldElementData( + private updateAutofillFieldElementData( attributeName: string, element: ElementWithOpId, dataTarget: AutofillField, @@ -1304,6 +1311,52 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte return attributeValue; } + /** + * Sets up an IntersectionObserver to observe found form + * field elements that are not viewable in the viewport. + */ + private setupIntersectionObserver() { + this.intersectionObserver = new IntersectionObserver(this.handleFormElementIntersection, { + root: null, + rootMargin: "0px", + threshold: 1.0, + }); + } + + /** + * Handles observed form field elements that are not viewable in the viewport. + * Will re-evaluate the visibility of the element and set up the autofill + * overlay listeners on the field if it is viewable. + * + * @param entries - The entries observed by the IntersectionObserver + */ + private handleFormElementIntersection = async (entries: IntersectionObserverEntry[]) => { + for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) { + const entry = entries[entryIndex]; + const formFieldElement = entry.target as ElementWithOpId; + if (this.elementInitializingIntersectionObserver.has(formFieldElement)) { + this.elementInitializingIntersectionObserver.delete(formFieldElement); + continue; + } + + const isViewable = + await this.domElementVisibilityService.isFormFieldViewable(formFieldElement); + if (!isViewable) { + continue; + } + + const cachedAutofillFieldElement = this.autofillFieldElements.get(formFieldElement); + cachedAutofillFieldElement.viewable = true; + + void this.autofillOverlayContentService?.setupAutofillOverlayListenerOnField( + formFieldElement, + cachedAutofillFieldElement, + ); + + this.intersectionObserver.unobserve(entry.target); + } + }; + /** * Destroys the CollectAutofillContentService. Clears all * timeouts and disconnects the mutation observer. @@ -1313,6 +1366,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte clearTimeout(this.updateAutofillElementsAfterMutationTimeout); } this.mutationObserver?.disconnect(); + this.intersectionObserver?.disconnect(); } } diff --git a/apps/browser/src/autofill/services/dom-element-visibility.service.ts b/apps/browser/src/autofill/services/dom-element-visibility.service.ts index acc5b12059..127ce84d91 100644 --- a/apps/browser/src/autofill/services/dom-element-visibility.service.ts +++ b/apps/browser/src/autofill/services/dom-element-visibility.service.ts @@ -66,7 +66,7 @@ class DomElementVisibilityService implements domElementVisibilityServiceInterfac */ private getElementStyle(element: HTMLElement, styleProperty: string): string { if (!this.cachedComputedStyle) { - this.cachedComputedStyle = (element.ownerDocument.defaultView || window).getComputedStyle( + this.cachedComputedStyle = (element.ownerDocument.defaultView || globalThis).getComputedStyle( element, ); } diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts index 5ea1284d1b..5a123bf835 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts @@ -47,7 +47,7 @@ const initEventCount = Object.freeze( ); let confirmSpy: jest.SpyInstance; -let windowSpy: jest.SpyInstance; +let windowLocationSpy: jest.SpyInstance; let savedURLs: string[] | null = ["https://bitwarden.com"]; function setMockWindowLocation({ protocol, @@ -56,11 +56,9 @@ function setMockWindowLocation({ protocol: "http:" | "https:"; hostname: string; }) { - windowSpy.mockImplementation(() => ({ - location: { - protocol, - hostname, - }, + windowLocationSpy.mockImplementation(() => ({ + protocol, + hostname, })); } @@ -76,8 +74,8 @@ describe("InsertAutofillContentService", () => { beforeEach(() => { document.body.innerHTML = mockLoginForm; - confirmSpy = jest.spyOn(window, "confirm"); - windowSpy = jest.spyOn(window, "window", "get"); + confirmSpy = jest.spyOn(globalThis, "confirm"); + windowLocationSpy = jest.spyOn(globalThis, "location", "get"); insertAutofillContentService = new InsertAutofillContentService( domElementVisibilityService, collectAutofillContentService, @@ -101,7 +99,7 @@ describe("InsertAutofillContentService", () => { afterEach(() => { jest.resetAllMocks(); - windowSpy.mockRestore(); + windowLocationSpy.mockRestore(); confirmSpy.mockRestore(); document.body.innerHTML = ""; }); @@ -245,8 +243,8 @@ describe("InsertAutofillContentService", () => { }); it("returns true if the frameElement has a sandbox attribute", () => { - Object.defineProperty(globalThis, "window", { - value: { frameElement: { hasAttribute: jest.fn(() => true) } }, + Object.defineProperty(globalThis, "frameElement", { + value: { hasAttribute: jest.fn(() => true) }, writable: true, }); @@ -991,11 +989,11 @@ describe("InsertAutofillContentService", () => { const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement; inputElement.value = "test"; jest.spyOn(inputElement, "focus"); - jest.spyOn(window, "String"); + jest.spyOn(globalThis, "String"); insertAutofillContentService["triggerFocusOnElement"](inputElement, true); - expect(window.String).toHaveBeenCalledWith(value); + expect(globalThis.String).toHaveBeenCalledWith(value); expect(inputElement.focus).toHaveBeenCalled(); expect(inputElement.value).toEqual(value); }); @@ -1005,11 +1003,11 @@ describe("InsertAutofillContentService", () => { const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement; inputElement.value = "test"; jest.spyOn(inputElement, "focus"); - jest.spyOn(window, "String"); + jest.spyOn(globalThis, "String"); insertAutofillContentService["triggerFocusOnElement"](inputElement, false); - expect(window.String).not.toHaveBeenCalledWith(); + expect(globalThis.String).not.toHaveBeenCalledWith(); expect(inputElement.focus).toHaveBeenCalled(); expect(inputElement.value).toEqual(value); }); diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.ts index dd14cadfa7..5cfa8091c4 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.ts @@ -65,8 +65,8 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf private fillingWithinSandboxedIframe() { return ( String(self.origin).toLowerCase() === "null" || - window.frameElement?.hasAttribute("sandbox") || - window.location.hostname === "" + globalThis.frameElement?.hasAttribute("sandbox") || + globalThis.location.hostname === "" ); } @@ -79,8 +79,8 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf */ private userCancelledInsecureUrlAutofill(savedUrls?: string[] | null): boolean { if ( - !savedUrls?.some((url) => url.startsWith(`https://${window.location.hostname}`)) || - window.location.protocol !== "http:" || + !savedUrls?.some((url) => url.startsWith(`https://${globalThis.location.hostname}`)) || + globalThis.location.protocol !== "http:" || !this.isPasswordFieldWithinDocument() ) { return false; @@ -88,10 +88,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf const confirmationWarning = [ chrome.i18n.getMessage("insecurePageWarning"), - chrome.i18n.getMessage("insecurePageWarningFillPrompt", [window.location.hostname]), + chrome.i18n.getMessage("insecurePageWarningFillPrompt", [globalThis.location.hostname]), ].join("\n\n"); - return !confirm(confirmationWarning); + return !globalThis.confirm(confirmationWarning); } /** @@ -129,10 +129,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf const confirmationWarning = [ chrome.i18n.getMessage("autofillIframeWarning"), - chrome.i18n.getMessage("autofillIframeWarningTip", [window.location.hostname]), + chrome.i18n.getMessage("autofillIframeWarningTip", [globalThis.location.hostname]), ].join("\n\n"); - return !confirm(confirmationWarning); + return !globalThis.confirm(confirmationWarning); } /** diff --git a/apps/browser/src/background/idle.background.ts b/apps/browser/src/background/idle.background.ts index 28c056cc0e..7b273459ad 100644 --- a/apps/browser/src/background/idle.background.ts +++ b/apps/browser/src/background/idle.background.ts @@ -11,7 +11,7 @@ const IdleInterval = 60 * 5; // 5 minutes export default class IdleBackground { private idle: typeof chrome.idle | typeof browser.idle | null; - private idleTimer: number = null; + private idleTimer: number | NodeJS.Timeout = null; private idleState = "active"; constructor( @@ -73,7 +73,7 @@ export default class IdleBackground { private pollIdle(handler: (newState: string) => void) { if (this.idleTimer != null) { - window.clearTimeout(this.idleTimer); + globalThis.clearTimeout(this.idleTimer); this.idleTimer = null; } // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -83,7 +83,7 @@ export default class IdleBackground { this.idleState = state; handler(state); } - this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000); + this.idleTimer = globalThis.setTimeout(() => this.pollIdle(handler), 5000); }); } } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c84cced018..9331481a1e 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -5,8 +5,11 @@ import { PinCryptoService, LoginStrategyServiceAbstraction, LoginStrategyService, + InternalUserDecryptionOptionsServiceAbstraction, + UserDecryptionOptionsService, AuthRequestServiceAbstraction, AuthRequestService, + LoginEmailServiceAbstraction, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; @@ -68,6 +71,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -91,6 +95,7 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; +import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; @@ -141,6 +146,7 @@ import { } from "@bitwarden/common/tools/password-strength"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service"; import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -198,10 +204,10 @@ import { BrowserApi } from "../platform/browser/browser-api"; import { flagEnabled } from "../platform/flags"; import { UpdateBadge } from "../platform/listeners/update-badge"; import { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service"; -import { BrowserConfigService } from "../platform/services/browser-config.service"; import { BrowserCryptoService } from "../platform/services/browser-crypto.service"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; import BrowserLocalStorageService from "../platform/services/browser-local-storage.service"; +import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service"; import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service"; import BrowserMessagingService from "../platform/services/browser-messaging.service"; import { BrowserStateService } from "../platform/services/browser-state.service"; @@ -211,7 +217,6 @@ import { BackgroundPlatformUtilsService } from "../platform/services/platform-ut import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; -import { BrowserSendService } from "../services/browser-send.service"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import FilelessImporterBackground from "../tools/background/fileless-importer.background"; import { BrowserFido2UserInterfaceService } from "../vault/fido2/browser-fido2-user-interface.service"; @@ -226,7 +231,7 @@ import RuntimeBackground from "./runtime.background"; export default class MainBackground { messagingService: MessagingServiceAbstraction; - storageService: AbstractStorageService; + storageService: AbstractStorageService & ObservableStorageService; secureStorageService: AbstractStorageService; memoryStorageService: AbstractMemoryStorageService; memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService; @@ -242,6 +247,7 @@ export default class MainBackground { environmentService: BrowserEnvironmentService; cipherService: CipherServiceAbstraction; folderService: InternalFolderServiceAbstraction; + userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction; collectionService: CollectionServiceAbstraction; vaultTimeoutService: VaultTimeoutService; vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction; @@ -254,6 +260,7 @@ export default class MainBackground { auditService: AuditServiceAbstraction; authService: AuthServiceAbstraction; loginStrategyService: LoginStrategyServiceAbstraction; + loginEmailService: LoginEmailServiceAbstraction; importApiService: ImportApiServiceAbstraction; importService: ImportServiceAbstraction; exportService: VaultExportServiceAbstraction; @@ -290,7 +297,7 @@ export default class MainBackground { avatarService: AvatarServiceAbstraction; mainContextMenuHandler: MainContextMenuHandler; cipherContextMenuHandler: CipherContextMenuHandler; - configService: BrowserConfigService; + configService: ConfigService; configApiService: ConfigApiServiceAbstraction; devicesApiService: DevicesApiServiceAbstraction; devicesService: DevicesServiceAbstraction; @@ -359,22 +366,28 @@ export default class MainBackground { this.cryptoFunctionService = new WebCryptoFunctionService(self); this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService); this.storageService = new BrowserLocalStorageService(); + + const mv3MemoryStorageCreator = (partitionName: string) => { + // TODO: Consider using multithreaded encrypt service in popup only context + return new LocalBackedSessionStorageService( + new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), + this.keyGenerationService, + new BrowserLocalStorageService(), + new BrowserMemoryStorageService(), + partitionName, + ); + }; + this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used this.memoryStorageService = BrowserApi.isManifestVersion(3) - ? new LocalBackedSessionStorageService( - new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), - this.keyGenerationService, - ) + ? mv3MemoryStorageCreator("stateService") : new MemoryStorageService(); this.memoryStorageForStateProviders = BrowserApi.isManifestVersion(3) - ? new LocalBackedSessionStorageService( - new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), - this.keyGenerationService, - ) + ? mv3MemoryStorageCreator("stateProviders") : new BackgroundMemoryStorageService(); const storageServiceProvider = new StorageServiceProvider( - this.storageService as BrowserLocalStorageService, + this.storageService, this.memoryStorageForStateProviders, ); @@ -440,6 +453,9 @@ export default class MainBackground { this.globalStateProvider, this.platformUtilsService.supportsSecureStorage(), this.secureStorageService, + this.keyGenerationService, + this.encryptService, + this.logService, ); const migrationRunner = new MigrationRunner( @@ -507,7 +523,6 @@ export default class MainBackground { this.badgeSettingsService = new BadgeSettingsService(this.stateProvider); this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.keyConnectorService = new KeyConnectorService( - this.stateService, this.cryptoService, this.apiService, this.tokenService, @@ -515,6 +530,7 @@ export default class MainBackground { this.organizationService, this.keyGenerationService, logoutCallback, + this.stateProvider, ); this.passwordStrengthService = new PasswordStrengthService(); @@ -539,17 +555,21 @@ export default class MainBackground { }; })(); + this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); + this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.deviceTrustCryptoService = new DeviceTrustCryptoService( this.keyGenerationService, this.cryptoFunctionService, this.cryptoService, this.encryptService, - this.stateService, this.appIdService, this.devicesApiService, this.i18nService, this.platformUtilsService, + this.stateProvider, + this.secureStorageService, + this.userDecryptionOptionsService, ); this.devicesService = new DevicesServiceImplementation(this.devicesApiService); @@ -562,14 +582,16 @@ export default class MainBackground { ); this.authService = new AuthService( + this.accountService, backgroundMessagingService, this.cryptoService, this.apiService, this.stateService, + this.tokenService, ); this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService( - this.activeUserStateProvider, + this.stateProvider, ); this.loginStrategyService = new LoginStrategyService( @@ -590,6 +612,7 @@ export default class MainBackground { this.policyService, this.deviceTrustCryptoService, this.authRequestService, + this.userDecryptionOptionsService, this.globalStateProvider, this.billingAccountProfileStateService, ); @@ -598,15 +621,13 @@ export default class MainBackground { this.userVerificationApiService = new UserVerificationApiService(this.apiService); - this.configApiService = new ConfigApiService(this.apiService, this.authService); + this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - this.configService = new BrowserConfigService( - this.stateService, + this.configService = new DefaultConfigService( this.configApiService, - this.authService, this.environmentService, this.logService, - true, + this.stateProvider, ); this.cipherService = new CipherService( @@ -631,6 +652,7 @@ export default class MainBackground { this.folderApiService = new FolderApiService(this.folderService, this.apiService); this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( + this.userDecryptionOptionsService, this.cryptoService, this.tokenService, this.policyService, @@ -650,6 +672,7 @@ export default class MainBackground { this.cryptoService, this.i18nService, this.userVerificationApiService, + this.userDecryptionOptionsService, this.pinCryptoService, this.logService, this.vaultTimeoutSettingsService, @@ -684,7 +707,7 @@ export default class MainBackground { logoutCallback, ); this.containerService = new ContainerService(this.cryptoService, this.encryptService); - this.sendService = new BrowserSendService( + this.sendService = new SendService( this.cryptoService, this.i18nService, this.keyGenerationService, @@ -717,6 +740,7 @@ export default class MainBackground { this.folderApiService, this.organizationService, this.sendApiService, + this.userDecryptionOptionsService, this.avatarService, logoutCallback, this.billingAccountProfileStateService, @@ -978,7 +1002,7 @@ export default class MainBackground { } async bootstrap() { - this.containerService.attachToGlobal(window); + this.containerService.attachToGlobal(self); await this.stateService.init(); @@ -990,7 +1014,6 @@ export default class MainBackground { this.filelessImporterBackground.init(); await this.commandsBackground.init(); - this.configService.init(); this.twoFactorService.init(); await this.overlayBackground.init(); @@ -1022,10 +1045,6 @@ export default class MainBackground { return new Promise((resolve) => { setTimeout(async () => { - await this.environmentService.setUrlsFromStorage(); - // Workaround to ignore stateService.activeAccount until URLs are set - // TODO: Remove this when implementing ticket PM-2637 - this.environmentService.initialized = true; if (!this.isPrivateMode) { await this.refreshBadge(); } @@ -1072,7 +1091,9 @@ export default class MainBackground { await this.stateService.setActiveUser(userId); if (userId == null) { - await this.stateService.setRememberedEmail(null); + this.loginEmailService.setRememberEmail(false); + await this.loginEmailService.saveEmailSettings(); + await this.refreshBadge(); await this.refreshMenu(); await this.overlayBackground.updateOverlayCiphers(); @@ -1117,7 +1138,6 @@ export default class MainBackground { this.policyService.clear(userId), this.passwordGenerationService.clear(userId), this.vaultTimeoutSettingsService.clear(userId), - this.keyConnectorService.clear(), this.vaultFilterService.clear(), this.biometricStateService.logout(userId), this.providerService.save(null, userId), diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index f422fc8550..a88bc051d8 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -1,7 +1,9 @@ +import { firstValueFrom } from "rxjs"; + import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +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"; @@ -44,7 +46,7 @@ export default class RuntimeBackground { private environmentService: BrowserEnvironmentService, private messagingService: MessagingService, private logService: LogService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private fido2Service: Fido2Service, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor @@ -87,7 +89,7 @@ export default class RuntimeBackground { BrowserApi.messageListener("runtime.background", backgroundMessageListener); if (this.main.popupOnlyContext) { - (window as any).bitwardenBackgroundMessageListener = backgroundMessageListener; + (self as any).bitwardenBackgroundMessageListener = backgroundMessageListener; } } @@ -134,7 +136,7 @@ export default class RuntimeBackground { await this.main.refreshBadge(); await this.main.refreshMenu(); }, 2000); - this.configService.triggerServerConfigFetch(); + await this.configService.ensureConfigFetched(); } break; case "openPopup": @@ -220,7 +222,8 @@ export default class RuntimeBackground { } break; case "authResult": { - const vaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { return; @@ -241,7 +244,8 @@ export default class RuntimeBackground { break; } case "webAuthnResult": { - const vaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { return; @@ -364,7 +368,8 @@ export default class RuntimeBackground { async sendBwInstalledMessageToVault() { try { - const vaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); const urlObj = new URL(vaultUrl); const tabs = await BrowserApi.tabsQuery({ url: `${urlObj.href}*` }); diff --git a/apps/browser/src/background/service-factories/send-service.factory.ts b/apps/browser/src/background/service-factories/send-service.factory.ts index bca46b4703..7c64bc076a 100644 --- a/apps/browser/src/background/service-factories/send-service.factory.ts +++ b/apps/browser/src/background/service-factories/send-service.factory.ts @@ -1,3 +1,4 @@ +import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { @@ -21,7 +22,6 @@ import { stateServiceFactory, StateServiceInitOptions, } from "../../platform/background/service-factories/state-service.factory"; -import { BrowserSendService } from "../../services/browser-send.service"; type SendServiceFactoryOptions = FactoryOptions; @@ -40,7 +40,7 @@ export function sendServiceFactory( "sendService", opts, async () => - new BrowserSendService( + new SendService( await cryptoServiceFactory(cache, opts), await i18nServiceFactory(cache, opts), await keyGenerationServiceFactory(cache, opts), diff --git a/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts b/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts index febc605bc8..92a1d83dd2 100644 --- a/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts +++ b/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts @@ -9,6 +9,10 @@ import { tokenServiceFactory, TokenServiceInitOptions, } from "../../auth/background/service-factories/token-service.factory"; +import { + userDecryptionOptionsServiceFactory, + UserDecryptionOptionsServiceInitOptions, +} from "../../auth/background/service-factories/user-decryption-options-service.factory"; import { biometricStateServiceFactory, BiometricStateServiceInitOptions, @@ -30,6 +34,7 @@ import { type VaultTimeoutSettingsServiceFactoryOptions = FactoryOptions; export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsServiceFactoryOptions & + UserDecryptionOptionsServiceInitOptions & CryptoServiceInitOptions & TokenServiceInitOptions & PolicyServiceInitOptions & @@ -46,6 +51,7 @@ export function vaultTimeoutSettingsServiceFactory( opts, async () => new VaultTimeoutSettingsService( + await userDecryptionOptionsServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts), await tokenServiceFactory(cache, opts), await policyServiceFactory(cache, opts), diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index e3f6e6353f..271b2c76a2 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": "2024.3.0", + "version": "2024.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -18,23 +18,24 @@ { "all_frames": false, "js": ["content/content-message-handler.js"], - "matches": ["http://*/*", "https://*/*", "file:///*"], + "matches": ["*://*/*", "file:///*"], + "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" }, { "all_frames": true, - "js": [ - "content/trigger-autofill-script-injection.js", - "content/fido2/trigger-fido2-content-script-injection.js" - ], - "matches": ["http://*/*", "https://*/*", "file:///*"], + "js": ["content/fido2/trigger-fido2-content-script-injection.js"], + "matches": ["https://*/*"], + "exclude_matches": ["https://*/*.xml*"], "run_at": "document_start" }, { "all_frames": true, "css": ["content/autofill.css"], - "matches": ["http://*/*", "https://*/*", "file:///*"], - "run_at": "document_end" + "js": ["content/trigger-autofill-script-injection.js"], + "matches": ["*://*/*", "file:///*"], + "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], + "run_at": "document_start" }, { "all_frames": false, @@ -57,6 +58,7 @@ }, "permissions": [ "", + "*://*/*", "tabs", "contextMenus", "storage", @@ -64,8 +66,6 @@ "clipboardRead", "clipboardWrite", "idle", - "http://*/*", - "https://*/*", "webRequest", "webRequestBlocking" ], diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 25215597f8..e7b0c0cd1e 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": "2024.3.0", + "version": "2024.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -19,16 +19,23 @@ { "all_frames": false, "js": ["content/content-message-handler.js"], - "matches": ["http://*/*", "https://*/*", "file:///*"], + "matches": ["*://*/*", "file:///*"], + "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" }, { "all_frames": true, - "js": [ - "content/trigger-autofill-script-injection.js", - "content/fido2/trigger-fido2-content-script-injection.js" - ], - "matches": ["http://*/*", "https://*/*", "file:///*"], + "js": ["content/fido2/trigger-fido2-content-script-injection.js"], + "matches": ["https://*/*"], + "exclude_matches": ["https://*/*.xml*"], + "run_at": "document_start" + }, + { + "all_frames": true, + "css": ["content/autofill.css"], + "js": ["content/trigger-autofill-script-injection.js", "content/misc-utils.js"], + "matches": ["*://*/*", "file:///*"], + "exclude_matches": ["*://*/*.xml*", "file:///*.xml*"], "run_at": "document_start" }, { @@ -36,18 +43,6 @@ "js": ["content/lp-fileless-importer.js"], "matches": ["https://lastpass.com/export.php"], "run_at": "document_start" - }, - { - "all_frames": true, - "css": ["content/autofill.css"], - "matches": ["http://*/*", "https://*/*", "file:///*"], - "run_at": "document_end" - }, - { - "all_frames": true, - "js": ["content/misc-utils.js"], - "matches": ["http://*/*", "https://*/*", "file:///*"], - "run_at": "document_end" } ], "background": { @@ -76,7 +71,7 @@ "offscreen" ], "optional_permissions": ["nativeMessaging", "privacy"], - "host_permissions": ["http://*/*", "https://*/*"], + "host_permissions": ["*://*/*"], "content_security_policy": { "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'", "sandbox": "sandbox allow-scripts; script-src 'self'" diff --git a/apps/browser/src/platform/background.ts b/apps/browser/src/platform/background.ts index b71b4d96b0..9c3510178c 100644 --- a/apps/browser/src/platform/background.ts +++ b/apps/browser/src/platform/background.ts @@ -1,42 +1,35 @@ +import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; + import MainBackground from "../background/main.background"; -import { onAlarmListener } from "./alarms/on-alarm-listener"; -import { registerAlarms } from "./alarms/register-alarms"; import { BrowserApi } from "./browser/browser-api"; -import { - contextMenusClickedListener, - onCommandListener, - onInstallListener, - runtimeMessageListener, - windowsOnFocusChangedListener, - tabsOnActivatedListener, - tabsOnReplacedListener, - tabsOnUpdatedListener, -} from "./listeners"; -if (BrowserApi.isManifestVersion(3)) { - chrome.commands.onCommand.addListener(onCommandListener); - chrome.runtime.onInstalled.addListener(onInstallListener); - chrome.alarms.onAlarm.addListener(onAlarmListener); - registerAlarms(); - chrome.windows.onFocusChanged.addListener(windowsOnFocusChangedListener); - chrome.tabs.onActivated.addListener(tabsOnActivatedListener); - chrome.tabs.onReplaced.addListener(tabsOnReplacedListener); - chrome.tabs.onUpdated.addListener(tabsOnUpdatedListener); - chrome.contextMenus.onClicked.addListener(contextMenusClickedListener); - BrowserApi.messageListener( - "runtime.background", - (message: { command: string }, sender, sendResponse) => { - // 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 - runtimeMessageListener(message, sender); - }, - ); -} else { - const bitwardenMain = ((window as any).bitwardenMain = new MainBackground()); - // 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 - bitwardenMain.bootstrap().then(() => { +const logService = new ConsoleLogService(false); +const bitwardenMain = ((self as any).bitwardenMain = new MainBackground()); +bitwardenMain + .bootstrap() + .then(() => { // Finished bootstrapping - }); + if (BrowserApi.isManifestVersion(3)) { + startHeartbeat().catch((error) => logService.error(error)); + } + }) + .catch((error) => logService.error(error)); + +/** + * Tracks when a service worker was last alive and extends the service worker + * lifetime by writing the current time to extension storage every 20 seconds. + */ +async function runHeartbeat() { + await chrome.storage.local.set({ "last-heartbeat": new Date().getTime() }); +} + +/** + * Starts the heartbeat interval which keeps the service worker alive. + */ +async function startHeartbeat() { + // Run the heartbeat once at service worker startup, then again every 20 seconds. + runHeartbeat() + .then(() => setInterval(runHeartbeat, 20 * 1000)) + .catch((error) => logService.error(error)); } diff --git a/apps/browser/src/platform/background/service-factories/billing-account-profile-state-service.factory.ts b/apps/browser/src/platform/background/service-factories/billing-account-profile-state-service.factory.ts index 80482eacb6..378707d6be 100644 --- a/apps/browser/src/platform/background/service-factories/billing-account-profile-state-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/billing-account-profile-state-service.factory.ts @@ -1,9 +1,8 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; -import { activeUserStateProviderFactory } from "./active-user-state-provider.factory"; import { FactoryOptions, CachedServices, factory } from "./factory-options"; -import { StateProviderInitOptions } from "./state-provider.factory"; +import { StateProviderInitOptions, stateProviderFactory } from "./state-provider.factory"; type BillingAccountProfileStateServiceFactoryOptions = FactoryOptions; @@ -21,8 +20,6 @@ export function billingAccountProfileStateServiceFactory( "billingAccountProfileStateService", opts, async () => - new DefaultBillingAccountProfileStateService( - await activeUserStateProviderFactory(cache, opts), - ), + new DefaultBillingAccountProfileStateService(await stateProviderFactory(cache, opts)), ); } diff --git a/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts b/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts index c0dbf1f475..3d7d508832 100644 --- a/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts @@ -2,9 +2,9 @@ import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstract import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; import { - authServiceFactory, - AuthServiceInitOptions, -} from "../../../auth/background/service-factories/auth-service.factory"; + tokenServiceFactory, + TokenServiceInitOptions, +} from "../../../auth/background/service-factories/token-service.factory"; import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; import { FactoryOptions, CachedServices, factory } from "./factory-options"; @@ -13,7 +13,7 @@ type ConfigApiServiceFactoyOptions = FactoryOptions; export type ConfigApiServiceInitOptions = ConfigApiServiceFactoyOptions & ApiServiceInitOptions & - AuthServiceInitOptions; + TokenServiceInitOptions; export function configApiServiceFactory( cache: { configApiService?: ConfigApiServiceAbstraction } & CachedServices, @@ -26,7 +26,7 @@ export function configApiServiceFactory( async () => new ConfigApiService( await apiServiceFactory(cache, opts), - await authServiceFactory(cache, opts), + await tokenServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/platform/background/service-factories/config-service.factory.ts b/apps/browser/src/platform/background/service-factories/config-service.factory.ts index 9c8b485c2a..a899f8fd9a 100644 --- a/apps/browser/src/platform/background/service-factories/config-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/config-service.factory.ts @@ -1,10 +1,5 @@ -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; - -import { - authServiceFactory, - AuthServiceInitOptions, -} from "../../../auth/background/service-factories/auth-service.factory"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { configApiServiceFactory, ConfigApiServiceInitOptions } from "./config-api.service.factory"; import { @@ -13,37 +8,30 @@ import { } from "./environment-service.factory"; import { FactoryOptions, CachedServices, factory } from "./factory-options"; import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; -import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; +import { stateProviderFactory, StateProviderInitOptions } from "./state-provider.factory"; -type ConfigServiceFactoryOptions = FactoryOptions & { - configServiceOptions?: { - subscribe?: boolean; - }; -}; +type ConfigServiceFactoryOptions = FactoryOptions; export type ConfigServiceInitOptions = ConfigServiceFactoryOptions & - StateServiceInitOptions & ConfigApiServiceInitOptions & - AuthServiceInitOptions & EnvironmentServiceInitOptions & - LogServiceInitOptions; + LogServiceInitOptions & + StateProviderInitOptions; export function configServiceFactory( - cache: { configService?: ConfigServiceAbstraction } & CachedServices, + cache: { configService?: ConfigService } & CachedServices, opts: ConfigServiceInitOptions, -): Promise { +): Promise { return factory( cache, "configService", opts, async () => - new ConfigService( - await stateServiceFactory(cache, opts), + new DefaultConfigService( await configApiServiceFactory(cache, opts), - await authServiceFactory(cache, opts), await environmentServiceFactory(cache, opts), await logServiceFactory(cache, opts), - opts.configServiceOptions?.subscribe ?? true, + await stateProviderFactory(cache, opts), ), ); } diff --git a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts index 6a854255f5..19d5a9c140 100644 --- a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts @@ -7,6 +7,7 @@ import { MemoryStorageService } from "@bitwarden/common/platform/services/memory import { BrowserApi } from "../../browser/browser-api"; import BrowserLocalStorageService from "../../services/browser-local-storage.service"; +import BrowserMemoryStorageService from "../../services/browser-memory-storage.service"; import { LocalBackedSessionStorageService } from "../../services/local-backed-session-storage.service"; import { BackgroundMemoryStorageService } from "../../storage/background-memory-storage.service"; @@ -17,13 +18,14 @@ import { keyGenerationServiceFactory, } from "./key-generation-service.factory"; -type StorageServiceFactoryOptions = FactoryOptions; - -export type DiskStorageServiceInitOptions = StorageServiceFactoryOptions; -export type SecureStorageServiceInitOptions = StorageServiceFactoryOptions; -export type MemoryStorageServiceInitOptions = StorageServiceFactoryOptions & +export type DiskStorageServiceInitOptions = FactoryOptions; +export type SecureStorageServiceInitOptions = FactoryOptions; +export type SessionStorageServiceInitOptions = FactoryOptions; +export type MemoryStorageServiceInitOptions = FactoryOptions & EncryptServiceInitOptions & - KeyGenerationServiceInitOptions; + KeyGenerationServiceInitOptions & + DiskStorageServiceInitOptions & + SessionStorageServiceInitOptions; export function diskStorageServiceFactory( cache: { diskStorageService?: AbstractStorageService } & CachedServices, @@ -47,6 +49,13 @@ export function secureStorageServiceFactory( return factory(cache, "secureStorageService", opts, () => new BrowserLocalStorageService()); } +export function sessionStorageServiceFactory( + cache: { sessionStorageService?: AbstractStorageService } & CachedServices, + opts: SessionStorageServiceInitOptions, +): Promise { + return factory(cache, "sessionStorageService", opts, () => new BrowserMemoryStorageService()); +} + export function memoryStorageServiceFactory( cache: { memoryStorageService?: AbstractMemoryStorageService } & CachedServices, opts: MemoryStorageServiceInitOptions, @@ -56,6 +65,9 @@ export function memoryStorageServiceFactory( return new LocalBackedSessionStorageService( await encryptServiceFactory(cache, opts), await keyGenerationServiceFactory(cache, opts), + await diskStorageServiceFactory(cache, opts), + await sessionStorageServiceFactory(cache, opts), + "serviceFactories", ); } return new MemoryStorageService(); diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 362aac1af9..b2ee66f051 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -351,11 +351,11 @@ export class BrowserApi { private static setupUnloadListeners() { // The MDN recommend using 'visibilitychange' but that event is fired any time the popup window is obscured as well // 'pagehide' works just like 'unload' but is compatible with the back/forward cache, so we prefer using that one - window.onpagehide = () => { + self.addEventListener("pagehide", () => { for (const [event, callback] of BrowserApi.trackedChromeEventListeners) { event.removeListener(callback); } - }; + }); } static sendMessage(subscriber: string, arg: any = {}) { @@ -423,7 +423,7 @@ export class BrowserApi { return; } - const currentHref = window.location.href; + const currentHref = self.location.href; views .filter((w) => w.location.href != null && !w.location.href.includes("background.html")) .filter((w) => !exemptCurrentHref || w.location.href !== currentHref) diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.ts index 02ae5cdb23..627036b80b 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.ts @@ -29,14 +29,14 @@ class OffscreenDocument implements OffscreenDocumentInterface { * @param message - The extension message containing the text to copy */ private async handleOffscreenCopyToClipboard(message: OffscreenDocumentExtensionMessage) { - await BrowserClipboardService.copy(window, message.text); + await BrowserClipboardService.copy(self, message.text); } /** * Reads the user's clipboard and returns the text. */ private async handleOffscreenReadFromClipboard() { - return await BrowserClipboardService.read(window); + return await BrowserClipboardService.read(self); } /** diff --git a/apps/browser/src/platform/services/browser-file-download.service.ts b/apps/browser/src/platform/popup/services/browser-file-download.service.ts similarity index 93% rename from apps/browser/src/platform/services/browser-file-download.service.ts rename to apps/browser/src/platform/popup/services/browser-file-download.service.ts index 8cb4d498a3..e9aaa639c4 100644 --- a/apps/browser/src/platform/services/browser-file-download.service.ts +++ b/apps/browser/src/platform/popup/services/browser-file-download.service.ts @@ -5,8 +5,8 @@ import { FileDownloadRequest } from "@bitwarden/common/platform/abstractions/fil import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SafariApp } from "../../browser/safariApp"; -import { BrowserApi } from "../browser/browser-api"; +import { SafariApp } from "../../../browser/safariApp"; +import { BrowserApi } from "../../browser/browser-api"; @Injectable() export class BrowserFileDownloadService implements FileDownloadService { diff --git a/apps/browser/src/platform/services/abstractions/browser-state.service.ts b/apps/browser/src/platform/services/abstractions/browser-state.service.ts index 88c2312762..82ec54975a 100644 --- a/apps/browser/src/platform/services/abstractions/browser-state.service.ts +++ b/apps/browser/src/platform/services/abstractions/browser-state.service.ts @@ -3,22 +3,9 @@ import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage import { Account } from "../../../models/account"; import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserGroupingsComponentState } from "../../../models/browserGroupingsComponentState"; import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; export abstract class BrowserStateService extends BaseStateServiceAbstraction { - getBrowserGroupingComponentState: ( - options?: StorageOptions, - ) => Promise; - setBrowserGroupingComponentState: ( - value: BrowserGroupingsComponentState, - options?: StorageOptions, - ) => Promise; - getBrowserVaultItemsComponentState: (options?: StorageOptions) => Promise; - setBrowserVaultItemsComponentState: ( - value: BrowserComponentState, - options?: StorageOptions, - ) => Promise; getBrowserSendComponentState: (options?: StorageOptions) => Promise; setBrowserSendComponentState: ( value: BrowserSendComponentState, diff --git a/apps/browser/src/platform/services/browser-config.service.ts b/apps/browser/src/platform/services/browser-config.service.ts deleted file mode 100644 index 557db4f33c..0000000000 --- a/apps/browser/src/platform/services/browser-config.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ReplaySubject } from "rxjs"; - -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; -import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; - -import { browserSession, sessionSync } from "../decorators/session-sync-observable"; - -@browserSession -export class BrowserConfigService extends ConfigService { - @sessionSync({ initializer: ServerConfig.fromJSON }) - protected _serverConfig: ReplaySubject; - - constructor( - stateService: StateService, - configApiService: ConfigApiServiceAbstraction, - authService: AuthService, - environmentService: EnvironmentService, - logService: LogService, - subscribe = false, - ) { - super(stateService, configApiService, authService, environmentService, logService, subscribe); - } -} diff --git a/apps/browser/src/platform/services/browser-environment.service.ts b/apps/browser/src/platform/services/browser-environment.service.ts index 77cf7de8ea..d7e22cf747 100644 --- a/apps/browser/src/platform/services/browser-environment.service.ts +++ b/apps/browser/src/platform/services/browser-environment.service.ts @@ -1,12 +1,15 @@ +import { firstValueFrom } from "rxjs"; + import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GroupPolicyEnvironment } from "../../admin-console/types/group-policy-environment"; import { devFlagEnabled, devFlagValue } from "../flags"; -export class BrowserEnvironmentService extends EnvironmentService { +export class BrowserEnvironmentService extends DefaultEnvironmentService { constructor( private logService: LogService, stateProvider: StateProvider, @@ -29,16 +32,18 @@ export class BrowserEnvironmentService extends EnvironmentService { return false; } - const env = await this.getManagedEnvironment(); + const managedEnv = await this.getManagedEnvironment(); + const env = await firstValueFrom(this.environment$); + const urls = env.getUrls(); return ( - env.base != this.baseUrl || - env.webVault != this.webVaultUrl || - env.api != this.webVaultUrl || - env.identity != this.identityUrl || - env.icons != this.iconsUrl || - env.notifications != this.notificationsUrl || - env.events != this.eventsUrl + managedEnv.base != urls.base || + managedEnv.webVault != urls.webVault || + managedEnv.api != urls.api || + managedEnv.identity != urls.identity || + managedEnv.icons != urls.icons || + managedEnv.notifications != urls.notifications || + managedEnv.events != urls.events ); } @@ -62,7 +67,7 @@ export class BrowserEnvironmentService extends EnvironmentService { async setUrlsToManagedEnvironment() { const env = await this.getManagedEnvironment(); - await this.setUrls({ + await this.setEnvironment(Region.SelfHosted, { base: env.base, webVault: env.webVault, api: env.api, diff --git a/apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts b/apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts index c2a6f8c5e1..0c7008473b 100644 --- a/apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts +++ b/apps/browser/src/platform/services/browser-messaging-private-mode-background.service.ts @@ -3,6 +3,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag export default class BrowserMessagingPrivateModeBackgroundService implements MessagingService { send(subscriber: string, arg: any = {}) { const message = Object.assign({}, { command: subscriber }, arg); - (window as any).bitwardenPopupMainMessageListener(message); + (self as any).bitwardenPopupMainMessageListener(message); } } diff --git a/apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts b/apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts index 5572ba1ba4..5883f61197 100644 --- a/apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts +++ b/apps/browser/src/platform/services/browser-messaging-private-mode-popup.service.ts @@ -3,6 +3,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag export default class BrowserMessagingPrivateModePopupService implements MessagingService { send(subscriber: string, arg: any = {}) { const message = Object.assign({}, { command: subscriber }, arg); - (window as any).bitwardenBackgroundMessageListener(message); + (self as any).bitwardenBackgroundMessageListener(message); } } diff --git a/apps/browser/src/platform/services/browser-state.service.spec.ts b/apps/browser/src/platform/services/browser-state.service.spec.ts index 3069b8f174..7e75b9b707 100644 --- a/apps/browser/src/platform/services/browser-state.service.spec.ts +++ b/apps/browser/src/platform/services/browser-state.service.spec.ts @@ -18,7 +18,6 @@ import { UserId } from "@bitwarden/common/types/guid"; import { Account } from "../../models/account"; import { BrowserComponentState } from "../../models/browserComponentState"; -import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; import { BrowserSendComponentState } from "../../models/browserSendComponentState"; import { BrowserStateService } from "./browser-state.service"; @@ -86,27 +85,6 @@ describe("Browser State Service", () => { ); }); - describe("getBrowserGroupingComponentState", () => { - it("should return a BrowserGroupingsComponentState", async () => { - state.accounts[userId].groupings = new BrowserGroupingsComponentState(); - - const actual = await sut.getBrowserGroupingComponentState(); - expect(actual).toBeInstanceOf(BrowserGroupingsComponentState); - }); - }); - - describe("getBrowserVaultItemsComponentState", () => { - it("should return a BrowserComponentState", async () => { - const componentState = new BrowserComponentState(); - componentState.scrollY = 0; - componentState.searchText = "test"; - state.accounts[userId].ciphers = componentState; - - const actual = await sut.getBrowserVaultItemsComponentState(); - expect(actual).toStrictEqual(componentState); - }); - }); - describe("getBrowserSendComponentState", () => { it("should return a BrowserSendComponentState", async () => { const sendState = new BrowserSendComponentState(); diff --git a/apps/browser/src/platform/services/browser-state.service.ts b/apps/browser/src/platform/services/browser-state.service.ts index f7ee74be21..ea410ee83a 100644 --- a/apps/browser/src/platform/services/browser-state.service.ts +++ b/apps/browser/src/platform/services/browser-state.service.ts @@ -16,7 +16,6 @@ import { StateService as BaseStateService } from "@bitwarden/common/platform/ser import { Account } from "../../models/account"; import { BrowserComponentState } from "../../models/browserComponentState"; -import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; import { BrowserSendComponentState } from "../../models/browserSendComponentState"; import { BrowserApi } from "../browser/browser-api"; import { browserSession, sessionSync } from "../decorators/session-sync-observable"; @@ -116,50 +115,6 @@ export class BrowserStateService ); } - async getBrowserGroupingComponentState( - options?: StorageOptions, - ): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.groupings; - } - - async setBrowserGroupingComponentState( - value: BrowserGroupingsComponentState, - options?: StorageOptions, - ): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.groupings = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - - async getBrowserVaultItemsComponentState( - options?: StorageOptions, - ): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.ciphers; - } - - async setBrowserVaultItemsComponentState( - value: BrowserComponentState, - options?: StorageOptions, - ): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.ciphers = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - async getBrowserSendComponentState(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) diff --git a/apps/browser/src/platform/services/i18n.service.ts b/apps/browser/src/platform/services/i18n.service.ts index 334ad8dc6c..a27c3935d7 100644 --- a/apps/browser/src/platform/services/i18n.service.ts +++ b/apps/browser/src/platform/services/i18n.service.ts @@ -25,6 +25,7 @@ export default class I18nService extends BaseI18nService { "bs", "ca", "cs", + "cy", "da", "de", "el", @@ -37,6 +38,7 @@ export default class I18nService extends BaseI18nService { "fi", "fil", "fr", + "gl", "he", "hi", "hr", @@ -51,9 +53,13 @@ export default class I18nService extends BaseI18nService { "lt", "lv", "ml", + "mr", + "my", "nb", + "ne", "nl", "nn", + "or", "pl", "pt-BR", "pt-PT", @@ -64,6 +70,7 @@ export default class I18nService extends BaseI18nService { "sl", "sr", "sv", + "te", "th", "tr", "uk", 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 fff9f2c28f..7740a22071 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 @@ -2,45 +2,70 @@ import { mock, MockProxy } from "jest-mock-extended"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { + AbstractMemoryStorageService, + AbstractStorageService, + StorageUpdate, +} from "@bitwarden/common/platform/abstractions/storage.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import BrowserLocalStorageService from "./browser-local-storage.service"; -import BrowserMemoryStorageService from "./browser-memory-storage.service"; import { LocalBackedSessionStorageService } from "./local-backed-session-storage.service"; -describe("Browser Session Storage Service", () => { +describe("LocalBackedSessionStorage", () => { let encryptService: MockProxy; let keyGenerationService: MockProxy; + let localStorageService: MockProxy; + let sessionStorageService: MockProxy; let cache: Map; const testObj = { a: 1, b: 2 }; - let localStorage: BrowserLocalStorageService; - let sessionStorage: BrowserMemoryStorageService; - const key = new SymmetricCryptoKey(Utils.fromUtf8ToArray("00000000000000000000000000000000")); let getSessionKeySpy: jest.SpyInstance; + let sendUpdateSpy: jest.SpyInstance; const mockEnc = (input: string) => Promise.resolve(new EncString("ENCRYPTED" + input)); let sut: LocalBackedSessionStorageService; + const mockExistingSessionKey = (key: SymmetricCryptoKey) => { + sessionStorageService.get.mockImplementation((storageKey) => { + if (storageKey === "localEncryptionKey_test") { + return Promise.resolve(key?.toJSON()); + } + + return Promise.reject("No implementation for " + storageKey); + }); + }; + beforeEach(() => { encryptService = mock(); keyGenerationService = mock(); + localStorageService = mock(); + sessionStorageService = mock(); - sut = new LocalBackedSessionStorageService(encryptService, keyGenerationService); + sut = new LocalBackedSessionStorageService( + encryptService, + keyGenerationService, + localStorageService, + sessionStorageService, + "test", + ); cache = sut["cache"]; - localStorage = sut["localStorage"]; - sessionStorage = sut["sessionStorage"]; + + keyGenerationService.createKeyWithPurpose.mockResolvedValue({ + derivedKey: key, + salt: "bitwarden-ephemeral", + material: null, // Not used + }); + getSessionKeySpy = jest.spyOn(sut, "getSessionEncKey"); getSessionKeySpy.mockResolvedValue(key); - }); - it("should exist", () => { - expect(sut).toBeInstanceOf(LocalBackedSessionStorageService); + sendUpdateSpy = jest.spyOn(sut, "sendUpdate"); + sendUpdateSpy.mockReturnValue(); }); describe("get", () => { @@ -54,7 +79,7 @@ describe("Browser Session Storage Service", () => { const session = { test: testObj }; beforeEach(() => { - jest.spyOn(sut, "getSessionEncKey").mockResolvedValue(key); + mockExistingSessionKey(key); }); describe("no session retrieved", () => { @@ -62,6 +87,7 @@ describe("Browser Session Storage Service", () => { let spy: jest.SpyInstance; beforeEach(async () => { spy = jest.spyOn(sut, "getLocalSession").mockResolvedValue(null); + localStorageService.get.mockResolvedValue(null); result = await sut.get("test"); }); @@ -123,31 +149,31 @@ describe("Browser Session Storage Service", () => { describe("remove", () => { it("should save null", async () => { - const spy = jest.spyOn(sut, "save"); - spy.mockResolvedValue(null); await sut.remove("test"); - expect(spy).toHaveBeenCalledWith("test", null); + expect(sendUpdateSpy).toHaveBeenCalledWith({ key: "test", updateType: "remove" }); }); }); describe("save", () => { describe("caching", () => { beforeEach(() => { - jest.spyOn(localStorage, "get").mockResolvedValue(null); - jest.spyOn(sessionStorage, "get").mockResolvedValue(null); - jest.spyOn(localStorage, "save").mockResolvedValue(); - jest.spyOn(sessionStorage, "save").mockResolvedValue(); + localStorageService.get.mockResolvedValue(null); + sessionStorageService.get.mockResolvedValue(null); + + localStorageService.save.mockResolvedValue(); + sessionStorageService.save.mockResolvedValue(); encryptService.encrypt.mockResolvedValue(mockEnc("{}")); }); it("should remove key from cache if value is null", async () => { cache.set("test", {}); - const deleteSpy = jest.spyOn(cache, "delete"); + const cacheSetSpy = jest.spyOn(cache, "set"); expect(cache.has("test")).toBe(true); await sut.save("test", null); - expect(cache.has("test")).toBe(false); - expect(deleteSpy).toHaveBeenCalledWith("test"); + // Don't remove from cache, just replace with null + expect(cache.get("test")).toBe(null); + expect(cacheSetSpy).toHaveBeenCalledWith("test", null); }); it("should set cache if value is non-null", async () => { @@ -197,7 +223,7 @@ describe("Browser Session Storage Service", () => { }); it("should return the stored symmetric crypto key", async () => { - jest.spyOn(sessionStorage, "get").mockResolvedValue({ ...key }); + sessionStorageService.get.mockResolvedValue({ ...key }); const result = await sut.getSessionEncKey(); expect(result).toStrictEqual(key); @@ -205,7 +231,6 @@ describe("Browser Session Storage Service", () => { describe("new key creation", () => { beforeEach(() => { - jest.spyOn(sessionStorage, "get").mockResolvedValue(null); keyGenerationService.createKeyWithPurpose.mockResolvedValue({ salt: "salt", material: null, @@ -218,25 +243,24 @@ describe("Browser Session Storage Service", () => { const result = await sut.getSessionEncKey(); expect(result).toStrictEqual(key); - expect(keyGenerationService.createKeyWithPurpose).toBeCalledTimes(1); + expect(keyGenerationService.createKeyWithPurpose).toHaveBeenCalledTimes(1); }); it("should store a symmetric crypto key if it makes one", async () => { const spy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue(); await sut.getSessionEncKey(); - expect(spy).toBeCalledWith(key); + expect(spy).toHaveBeenCalledWith(key); }); }); }); describe("getLocalSession", () => { it("should return null if session is null", async () => { - const spy = jest.spyOn(localStorage, "get").mockResolvedValue(null); const result = await sut.getLocalSession(key); expect(result).toBeNull(); - expect(spy).toBeCalledWith("session"); + expect(localStorageService.get).toHaveBeenCalledWith("session_test"); }); describe("non-null sessions", () => { @@ -245,7 +269,7 @@ describe("Browser Session Storage Service", () => { const decryptedSession = JSON.stringify(session); beforeEach(() => { - jest.spyOn(localStorage, "get").mockResolvedValue(encSession.encryptedString); + localStorageService.get.mockResolvedValue(encSession.encryptedString); }); it("should decrypt returned sessions", async () => { @@ -267,13 +291,12 @@ describe("Browser Session Storage Service", () => { it("should remove state if decryption fails", async () => { encryptService.decryptToUtf8.mockResolvedValue(null); const setSessionEncKeySpy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue(); - const removeLocalSessionSpy = jest.spyOn(localStorage, "remove").mockResolvedValue(); const result = await sut.getLocalSession(key); expect(result).toBeNull(); expect(setSessionEncKeySpy).toHaveBeenCalledWith(null); - expect(removeLocalSessionSpy).toHaveBeenCalledWith("session"); + expect(localStorageService.remove).toHaveBeenCalledWith("session_test"); }); }); }); @@ -284,7 +307,7 @@ describe("Browser Session Storage Service", () => { it("should encrypt a stringified session", async () => { encryptService.encrypt.mockImplementation(mockEnc); - jest.spyOn(localStorage, "save").mockResolvedValue(); + localStorageService.save.mockResolvedValue(); await sut.setLocalSession(testSession, key); expect(encryptService.encrypt).toHaveBeenNthCalledWith(1, testJSON, key); @@ -292,32 +315,31 @@ describe("Browser Session Storage Service", () => { it("should remove local session if null", async () => { encryptService.encrypt.mockResolvedValue(null); - const spy = jest.spyOn(localStorage, "remove").mockResolvedValue(); await sut.setLocalSession(null, key); - expect(spy).toHaveBeenCalledWith("session"); + expect(localStorageService.remove).toHaveBeenCalledWith("session_test"); }); it("should save encrypted string", async () => { encryptService.encrypt.mockImplementation(mockEnc); - const spy = jest.spyOn(localStorage, "save").mockResolvedValue(); await sut.setLocalSession(testSession, key); - expect(spy).toHaveBeenCalledWith("session", (await mockEnc(testJSON)).encryptedString); + expect(localStorageService.save).toHaveBeenCalledWith( + "session_test", + (await mockEnc(testJSON)).encryptedString, + ); }); }); describe("setSessionKey", () => { it("should remove if null", async () => { - const spy = jest.spyOn(sessionStorage, "remove").mockResolvedValue(); await sut.setSessionEncKey(null); - expect(spy).toHaveBeenCalledWith("localEncryptionKey"); + expect(sessionStorageService.remove).toHaveBeenCalledWith("localEncryptionKey_test"); }); it("should save key when not null", async () => { - const spy = jest.spyOn(sessionStorage, "save").mockResolvedValue(); await sut.setSessionEncKey(key); - expect(spy).toHaveBeenCalledWith("localEncryptionKey", key); + expect(sessionStorageService.save).toHaveBeenCalledWith("localEncryptionKey_test", key); }); }); }); 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 b2823ffe4b..3f01e4169e 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 @@ -1,40 +1,60 @@ -import { Subject } from "rxjs"; +import { Observable, Subject, filter, map, merge, share, tap } from "rxjs"; import { Jsonify } from "type-fest"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { AbstractMemoryStorageService, + AbstractStorageService, + ObservableStorageService, StorageUpdate, } from "@bitwarden/common/platform/abstractions/storage.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { MemoryStorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { fromChromeEvent } from "../browser/from-chrome-event"; import { devFlag } from "../decorators/dev-flag.decorator"; import { devFlagEnabled } from "../flags"; -import BrowserLocalStorageService from "./browser-local-storage.service"; -import BrowserMemoryStorageService from "./browser-memory-storage.service"; - -const keys = { - encKey: "localEncryptionKey", - sessionKey: "session", -}; - -export class LocalBackedSessionStorageService extends AbstractMemoryStorageService { +export class LocalBackedSessionStorageService + extends AbstractMemoryStorageService + implements ObservableStorageService +{ private cache = new Map(); - private localStorage = new BrowserLocalStorageService(); - private sessionStorage = new BrowserMemoryStorageService(); private updatesSubject = new Subject(); - updates$; + + private commandName = `localBackedSessionStorage_${this.name}`; + private encKey = `localEncryptionKey_${this.name}`; + private sessionKey = `session_${this.name}`; + + updates$: Observable; constructor( private encryptService: EncryptService, private keyGenerationService: KeyGenerationService, + private localStorage: AbstractStorageService, + private sessionStorage: AbstractStorageService, + private name: string, ) { super(); - this.updates$ = this.updatesSubject.asObservable(); + + const remoteObservable = fromChromeEvent(chrome.runtime.onMessage).pipe( + filter(([msg]) => msg.command === this.commandName), + map(([msg]) => msg.update as StorageUpdate), + tap((update) => { + if (update.updateType === "remove") { + this.cache.set(update.key, null); + } else { + this.cache.delete(update.key); + } + }), + share(), + ); + + remoteObservable.subscribe(); + + this.updates$ = merge(this.updatesSubject.asObservable(), remoteObservable); } get valuesRequireDeserialization(): boolean { @@ -70,23 +90,37 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageServi async save(key: string, obj: T): Promise { if (obj == null) { - this.cache.delete(key); - } else { - this.cache.set(key, obj); + return await this.remove(key); } + this.cache.set(key, obj); + await this.updateLocalSessionValue(key, obj); + this.sendUpdate({ key, updateType: "save" }); + } + + async remove(key: string): Promise { + this.cache.set(key, null); + await this.updateLocalSessionValue(key, null); + this.sendUpdate({ key, updateType: "remove" }); + } + + sendUpdate(storageUpdate: StorageUpdate) { + this.updatesSubject.next(storageUpdate); + void chrome.runtime.sendMessage({ + command: this.commandName, + update: storageUpdate, + }); + } + + private async updateLocalSessionValue(key: string, obj: T) { const sessionEncKey = await this.getSessionEncKey(); const localSession = (await this.getLocalSession(sessionEncKey)) ?? {}; localSession[key] = obj; await this.setLocalSession(localSession, sessionEncKey); } - async remove(key: string): Promise { - await this.save(key, null); - } - async getLocalSession(encKey: SymmetricCryptoKey): Promise> { - const local = await this.localStorage.get(keys.sessionKey); + const local = await this.localStorage.get(this.sessionKey); if (local == null) { return null; @@ -100,7 +134,7 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageServi if (sessionJson == null) { // Error with decryption -- session is lost, delete state and key and start over await this.setSessionEncKey(null); - await this.localStorage.remove(keys.sessionKey); + await this.localStorage.remove(this.sessionKey); return null; } return JSON.parse(sessionJson); @@ -119,9 +153,9 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageServi // Make sure we're storing the jsonified version of the session const jsonSession = JSON.parse(JSON.stringify(session)); if (session == null) { - await this.localStorage.remove(keys.sessionKey); + await this.localStorage.remove(this.sessionKey); } else { - await this.localStorage.save(keys.sessionKey, jsonSession); + await this.localStorage.save(this.sessionKey, jsonSession); } } @@ -130,13 +164,13 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageServi const encSession = await this.encryptService.encrypt(jsonSession, key); if (encSession == null) { - return await this.localStorage.remove(keys.sessionKey); + return await this.localStorage.remove(this.sessionKey); } - await this.localStorage.save(keys.sessionKey, encSession.encryptedString); + await this.localStorage.save(this.sessionKey, encSession.encryptedString); } async getSessionEncKey(): Promise { - let storedKey = await this.sessionStorage.get(keys.encKey); + let storedKey = await this.sessionStorage.get(this.encKey); if (storedKey == null || Object.keys(storedKey).length == 0) { const generatedKey = await this.keyGenerationService.createKeyWithPurpose( 128, @@ -153,9 +187,9 @@ export class LocalBackedSessionStorageService extends AbstractMemoryStorageServi async setSessionEncKey(input: SymmetricCryptoKey): Promise { if (input == null) { - await this.sessionStorage.remove(keys.encKey); + await this.sessionStorage.remove(this.encKey); } else { - await this.sessionStorage.save(keys.encKey, input); + await this.sessionStorage.save(this.encKey, input); } } } diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 7f2d8f682a..68e519c406 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -13,6 +13,7 @@ import { BrowserApi } from "../platform/browser/browser-api"; import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; import { ForegroundPlatformUtilsService } from "../platform/services/platform-utils/foreground-platform-utils.service"; +import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; import { routerTransition } from "./app-routing.animations"; import { DesktopSyncVerificationDialogComponent } from "./components/desktop-sync-verification-dialog.component"; @@ -38,6 +39,7 @@ export class AppComponent implements OnInit, OnDestroy { private i18nService: I18nService, private router: Router, private stateService: BrowserStateService, + private vaultBrowserStateService: VaultBrowserStateService, private cipherService: CipherService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, @@ -142,7 +144,7 @@ export class AppComponent implements OnInit, OnDestroy { } }; - (window as any).bitwardenPopupMainMessageListener = bitwardenPopupMainMessageListener; + (self as any).bitwardenPopupMainMessageListener = bitwardenPopupMainMessageListener; this.browserMessagingApi.messageListener("app.component", bitwardenPopupMainMessageListener); // eslint-disable-next-line rxjs/no-async-subscribe @@ -229,8 +231,8 @@ export class AppComponent implements OnInit, OnDestroy { } await Promise.all([ - this.stateService.setBrowserGroupingComponentState(null), - this.stateService.setBrowserVaultItemsComponentState(null), + this.vaultBrowserStateService.setBrowserGroupingsComponentState(null), + this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null), this.stateService.setBrowserSendComponentState(null), this.stateService.setBrowserSendTypeComponentState(null), ]); diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index e438f2588b..d179868448 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -16,6 +16,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; import { AvatarModule, ButtonModule } from "@bitwarden/components"; +import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { AccountComponent } from "../auth/popup/account-switching/account.component"; @@ -107,6 +108,7 @@ import "../platform/popup/locales"; AvatarModule, AccountComponent, ButtonModule, + ExportScopeCalloutComponent, ], declarations: [ ActionButtonsComponent, diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 85983c6391..73da455941 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -17,6 +17,8 @@ body { body { width: 375px !important; height: 600px !important; + position: relative; + min-height: 100vh; overflow: hidden; color: $text-color; background-color: $background-color; @@ -169,7 +171,7 @@ cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb, } } -header:not(bit-callout header) { +header:not(bit-callout header, bit-dialog header) { height: 44px; display: flex; diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index b0e80ab960..4036ace31f 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -5,7 +5,6 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; @@ -19,7 +18,6 @@ export class InitService { private stateService: StateServiceAbstraction, private logService: LogServiceAbstraction, private themingService: AbstractThemingService, - private configService: ConfigService, @Inject(DOCUMENT) private document: Document, ) {} @@ -55,7 +53,6 @@ export class InitService { this.logService.info("Force redraw is on"); } - this.configService.init(); this.setupVaultPopupHeartbeat(); }; } diff --git a/apps/browser/src/popup/services/popup-search.service.ts b/apps/browser/src/popup/services/popup-search.service.ts index 7eea1265a2..bc5e565e6c 100644 --- a/apps/browser/src/popup/services/popup-search.service.ts +++ b/apps/browser/src/popup/services/popup-search.service.ts @@ -1,14 +1,14 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SearchService } from "@bitwarden/common/services/search.service"; export class PopupSearchService extends SearchService { constructor( private mainSearchService: SearchService, - consoleLogService: ConsoleLogService, + logService: LogService, i18nService: I18nService, ) { - super(consoleLogService, i18nService); + super(logService, i18nService); } clearIndex() { diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 52de0303fa..6d0f73f206 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -1,22 +1,24 @@ import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; +import { Router } from "@angular/router"; import { ToastrService } from "ngx-toastr"; import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards"; import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service"; +import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { MEMORY_STORAGE, SECURE_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE, SYSTEM_THEME_OBSERVABLE, + SafeInjectionToken, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction, } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; @@ -28,13 +30,11 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/ab import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; -import { LoginService } from "@bitwarden/common/auth/services/login.service"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -47,19 +47,13 @@ import { UserNotificationSettingsService, UserNotificationSettingsServiceAbstraction, } from "@bitwarden/common/autofill/services/user-notification-settings.service"; -import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { FileUploadService } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; -import { - LogService, - LogService as LogServiceAbstraction, -} from "@bitwarden/common/platform/abstractions/log.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 as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; @@ -69,7 +63,6 @@ import { } from "@bitwarden/common/platform/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; @@ -82,13 +75,6 @@ import { import { SearchService } from "@bitwarden/common/services/search.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service"; -import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { - InternalSendService as InternalSendServiceAbstraction, - SendService, -} from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; @@ -96,7 +82,6 @@ import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vau import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { DialogService } from "@bitwarden/components"; -import { ImportServiceAbstraction } from "@bitwarden/importer/core"; import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; import { UnauthGuardService } from "../../auth/popup/services"; @@ -105,10 +90,9 @@ import MainBackground from "../../background/main.background"; import { Account } from "../../models/account"; import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; +import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; -import { BrowserConfigService } from "../../platform/services/browser-config.service"; import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; -import { BrowserFileDownloadService } from "../../platform/services/browser-file-download.service"; import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service"; import BrowserMessagingService from "../../platform/services/browser-messaging.service"; @@ -117,8 +101,8 @@ import I18nService from "../../platform/services/i18n.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; -import { BrowserSendService } from "../../services/browser-send.service"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; +import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service"; import { VaultFilterService } from "../../vault/services/vault-filter.service"; import { DebounceNavigationService } from "./debounce-navigation.service"; @@ -147,376 +131,353 @@ function getBgService(service: keyof MainBackground) { }; } +/** + * Provider definitions used in the ngModule. + * Add your provider definition here using the safeProvider function as a wrapper. This will give you type safety. + * If you need help please ask for it, do NOT change the type of this array. + */ +const safeProviders: SafeProvider[] = [ + safeProvider(InitService), + safeProvider(DebounceNavigationService), + safeProvider(DialogService), + safeProvider(PopupCloseWarningService), + safeProvider({ + provide: APP_INITIALIZER as SafeInjectionToken<() => Promise>, + useFactory: (initService: InitService) => initService.init(), + deps: [InitService], + multi: true, + }), + safeProvider({ + provide: BaseUnauthGuardService, + useClass: UnauthGuardService, + deps: [AuthServiceAbstraction, Router], + }), + safeProvider({ + provide: MessagingService, + useFactory: () => { + return needsBackgroundInit + ? new BrowserMessagingPrivateModePopupService() + : new BrowserMessagingService(); + }, + deps: [], + }), + safeProvider({ + provide: TwoFactorService, + useFactory: getBgService("twoFactorService"), + deps: [], + }), + safeProvider({ + provide: AuthServiceAbstraction, + useFactory: getBgService("authService"), + deps: [], + }), + safeProvider({ + provide: LoginStrategyServiceAbstraction, + useFactory: getBgService("loginStrategyService"), + deps: [], + }), + safeProvider({ + provide: SsoLoginServiceAbstraction, + useFactory: getBgService("ssoLoginService"), + deps: [], + }), + safeProvider({ + provide: SearchServiceAbstraction, + useFactory: (logService: LogService, i18nService: I18nServiceAbstraction) => { + return new PopupSearchService( + getBgService("searchService")(), + logService, + i18nService, + ); + }, + deps: [LogService, I18nServiceAbstraction], + }), + safeProvider({ + provide: CipherFileUploadService, + useFactory: getBgService("cipherFileUploadService"), + deps: [], + }), + safeProvider({ + provide: CipherService, + useFactory: getBgService("cipherService"), + deps: [], + }), + safeProvider({ + provide: CryptoFunctionService, + useFactory: () => new WebCryptoFunctionService(window), + deps: [], + }), + safeProvider({ + provide: CollectionService, + useFactory: getBgService("collectionService"), + deps: [], + }), + safeProvider({ + provide: LogService, + useFactory: (platformUtilsService: PlatformUtilsService) => + new ConsoleLogService(platformUtilsService.isDev()), + deps: [PlatformUtilsService], + }), + safeProvider({ + provide: EnvironmentService, + useExisting: BrowserEnvironmentService, + }), + safeProvider({ + provide: BrowserEnvironmentService, + useClass: BrowserEnvironmentService, + deps: [LogService, StateProvider, AccountServiceAbstraction], + }), + safeProvider({ + provide: TotpService, + useFactory: getBgService("totpService"), + deps: [], + }), + safeProvider({ + provide: I18nServiceAbstraction, + useFactory: (globalStateProvider: GlobalStateProvider) => { + return new I18nService(BrowserApi.getUILanguage(), globalStateProvider); + }, + deps: [GlobalStateProvider], + }), + safeProvider({ + provide: CryptoService, + useFactory: (encryptService: EncryptService) => { + const cryptoService = getBgService("cryptoService")(); + new ContainerService(cryptoService, encryptService).attachToGlobal(self); + return cryptoService; + }, + deps: [EncryptService], + }), + safeProvider({ + provide: AuthRequestServiceAbstraction, + useFactory: getBgService("authRequestService"), + deps: [], + }), + safeProvider({ + provide: DeviceTrustCryptoServiceAbstraction, + useFactory: getBgService("deviceTrustCryptoService"), + deps: [], + }), + safeProvider({ + provide: DevicesServiceAbstraction, + useFactory: getBgService("devicesService"), + deps: [], + }), + safeProvider({ + provide: PlatformUtilsService, + useExisting: ForegroundPlatformUtilsService, + }), + safeProvider({ + provide: ForegroundPlatformUtilsService, + useClass: ForegroundPlatformUtilsService, + useFactory: (sanitizer: DomSanitizer, toastrService: ToastrService) => { + return new ForegroundPlatformUtilsService( + sanitizer, + toastrService, + (clipboardValue: string, clearMs: number) => { + void BrowserApi.sendMessage("clearClipboard", { clipboardValue, clearMs }); + }, + async () => { + const response = await BrowserApi.sendMessageWithResponse<{ + result: boolean; + error: string; + }>("biometricUnlock"); + if (!response.result) { + throw response.error; + } + return response.result; + }, + window, + ); + }, + deps: [DomSanitizer, ToastrService], + }), + safeProvider({ + provide: PasswordGenerationServiceAbstraction, + useFactory: getBgService("passwordGenerationService"), + deps: [], + }), + safeProvider({ + provide: SyncService, + useFactory: getBgService("syncService"), + deps: [], + }), + safeProvider({ + provide: DomainSettingsService, + useClass: DefaultDomainSettingsService, + deps: [StateProvider], + }), + safeProvider({ + provide: AbstractStorageService, + useClass: BrowserLocalStorageService, + deps: [], + }), + safeProvider({ + provide: AutofillService, + useFactory: getBgService("autofillService"), + deps: [], + }), + safeProvider({ + provide: VaultExportServiceAbstraction, + useFactory: getBgService("exportService"), + deps: [], + }), + safeProvider({ + provide: KeyConnectorService, + useFactory: getBgService("keyConnectorService"), + deps: [], + }), + safeProvider({ + provide: UserVerificationService, + useFactory: getBgService("userVerificationService"), + deps: [], + }), + safeProvider({ + provide: VaultTimeoutSettingsService, + useFactory: getBgService("vaultTimeoutSettingsService"), + deps: [], + }), + safeProvider({ + provide: VaultTimeoutService, + useFactory: getBgService("vaultTimeoutService"), + deps: [], + }), + safeProvider({ + provide: NotificationsService, + useFactory: getBgService("notificationsService"), + deps: [], + }), + safeProvider({ + provide: VaultFilterService, + useClass: VaultFilterService, + deps: [ + OrganizationService, + FolderServiceAbstraction, + CipherService, + CollectionService, + PolicyService, + StateProvider, + AccountServiceAbstraction, + ], + }), + safeProvider({ + provide: SECURE_STORAGE, + useExisting: AbstractStorageService, // Secure storage is not available in the browser, so we use normal storage instead and warn users when it is used. + }), + safeProvider({ + provide: MEMORY_STORAGE, + useFactory: getBgService("memoryStorageService"), + deps: [], + }), + safeProvider({ + provide: OBSERVABLE_MEMORY_STORAGE, + useClass: ForegroundMemoryStorageService, + deps: [], + }), + safeProvider({ + provide: OBSERVABLE_DISK_STORAGE, + useExisting: AbstractStorageService, + }), + safeProvider({ + provide: VaultBrowserStateService, + useFactory: (stateProvider: StateProvider) => { + return new VaultBrowserStateService(stateProvider); + }, + deps: [StateProvider], + }), + safeProvider({ + provide: StateServiceAbstraction, + useFactory: ( + storageService: AbstractStorageService, + secureStorageService: AbstractStorageService, + memoryStorageService: AbstractMemoryStorageService, + logService: LogService, + accountService: AccountServiceAbstraction, + environmentService: EnvironmentService, + tokenService: TokenService, + migrationRunner: MigrationRunner, + ) => { + return new BrowserStateService( + storageService, + secureStorageService, + memoryStorageService, + logService, + new StateFactory(GlobalState, Account), + accountService, + environmentService, + tokenService, + migrationRunner, + ); + }, + deps: [ + AbstractStorageService, + SECURE_STORAGE, + MEMORY_STORAGE, + LogService, + AccountServiceAbstraction, + EnvironmentService, + TokenService, + MigrationRunner, + ], + }), + safeProvider({ + provide: UsernameGenerationServiceAbstraction, + useFactory: getBgService("usernameGenerationService"), + deps: [], + }), + safeProvider({ + provide: BaseStateServiceAbstraction, + useExisting: StateServiceAbstraction, + deps: [], + }), + safeProvider({ + provide: FileDownloadService, + useClass: BrowserFileDownloadService, + deps: [], + }), + safeProvider({ + provide: SYSTEM_THEME_OBSERVABLE, + useFactory: (platformUtilsService: PlatformUtilsService) => { + // Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light. + // In Safari, we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed. + let windowContext = window; + const backgroundWindow = BrowserApi.getBackgroundPage(); + if (platformUtilsService.isSafari() && backgroundWindow) { + windowContext = backgroundWindow; + } + + return AngularThemingService.createSystemThemeFromWindow(windowContext); + }, + deps: [PlatformUtilsService], + }), + safeProvider({ + provide: FilePopoutUtilsService, + useFactory: (platformUtilsService: PlatformUtilsService) => { + return new FilePopoutUtilsService(platformUtilsService); + }, + deps: [PlatformUtilsService], + }), + safeProvider({ + provide: DerivedStateProvider, + useClass: ForegroundDerivedStateProvider, + deps: [OBSERVABLE_MEMORY_STORAGE, NgZone], + }), + safeProvider({ + provide: AutofillSettingsServiceAbstraction, + useClass: AutofillSettingsService, + deps: [StateProvider, PolicyService], + }), + safeProvider({ + provide: UserNotificationSettingsServiceAbstraction, + useClass: UserNotificationSettingsService, + deps: [StateProvider], + }), +]; + @NgModule({ imports: [JslibServicesModule], declarations: [], - providers: [ - InitService, - DebounceNavigationService, - DialogService, - PopupCloseWarningService, - { - provide: APP_INITIALIZER, - useFactory: (initService: InitService) => initService.init(), - deps: [InitService], - multi: true, - }, - { provide: BaseUnauthGuardService, useClass: UnauthGuardService }, - { - provide: MessagingService, - useFactory: () => { - return needsBackgroundInit - ? new BrowserMessagingPrivateModePopupService() - : new BrowserMessagingService(); - }, - }, - { - provide: TwoFactorService, - useFactory: getBgService("twoFactorService"), - deps: [], - }, - { - provide: AuthServiceAbstraction, - useFactory: getBgService("authService"), - deps: [], - }, - { - provide: LoginStrategyServiceAbstraction, - useFactory: getBgService("loginStrategyService"), - }, - { - provide: SsoLoginServiceAbstraction, - useFactory: getBgService("ssoLoginService"), - deps: [], - }, - { - provide: SearchServiceAbstraction, - useFactory: (logService: ConsoleLogService, i18nService: I18nServiceAbstraction) => { - return new PopupSearchService( - getBgService("searchService")(), - logService, - i18nService, - ); - }, - deps: [LogServiceAbstraction, I18nServiceAbstraction], - }, - { - provide: CipherFileUploadService, - useFactory: getBgService("cipherFileUploadService"), - deps: [], - }, - { provide: CipherService, useFactory: getBgService("cipherService"), deps: [] }, - { - provide: CryptoFunctionService, - useFactory: () => new WebCryptoFunctionService(window), - deps: [], - }, - { - provide: CollectionService, - useFactory: getBgService("collectionService"), - deps: [], - }, - { - provide: LogServiceAbstraction, - useFactory: (platformUtilsService: PlatformUtilsService) => - new ConsoleLogService(platformUtilsService.isDev()), - deps: [PlatformUtilsService], - }, - { - provide: BrowserEnvironmentService, - useExisting: EnvironmentService, - }, - { - provide: EnvironmentService, - useFactory: getBgService("environmentService"), - deps: [], - }, - { provide: TotpService, useFactory: getBgService("totpService"), deps: [] }, - { - provide: I18nServiceAbstraction, - useFactory: (globalStateProvider: GlobalStateProvider) => { - return new I18nService(BrowserApi.getUILanguage(), globalStateProvider); - }, - deps: [GlobalStateProvider], - }, - { - provide: CryptoService, - useFactory: (encryptService: EncryptService) => { - const cryptoService = getBgService("cryptoService")(); - new ContainerService(cryptoService, encryptService).attachToGlobal(self); - return cryptoService; - }, - deps: [EncryptService], - }, - { - provide: AuthRequestServiceAbstraction, - useFactory: getBgService("authRequestService"), - deps: [], - }, - { - provide: DeviceTrustCryptoServiceAbstraction, - useFactory: getBgService("deviceTrustCryptoService"), - deps: [], - }, - { - provide: DevicesServiceAbstraction, - useFactory: getBgService("devicesService"), - deps: [], - }, - { - provide: PlatformUtilsService, - useExisting: ForegroundPlatformUtilsService, - }, - { - provide: ForegroundPlatformUtilsService, - useClass: ForegroundPlatformUtilsService, - useFactory: (sanitizer: DomSanitizer, toastrService: ToastrService) => { - return new ForegroundPlatformUtilsService( - sanitizer, - toastrService, - (clipboardValue: string, clearMs: number) => { - void BrowserApi.sendMessage("clearClipboard", { clipboardValue, clearMs }); - }, - async () => { - const response = await BrowserApi.sendMessageWithResponse<{ - result: boolean; - error: string; - }>("biometricUnlock"); - if (!response.result) { - throw response.error; - } - return response.result; - }, - window, - ); - }, - deps: [DomSanitizer, ToastrService], - }, - { - provide: PasswordStrengthServiceAbstraction, - useFactory: getBgService("passwordStrengthService"), - deps: [], - }, - { - provide: PasswordGenerationServiceAbstraction, - useFactory: getBgService("passwordGenerationService"), - deps: [], - }, - { - provide: SendService, - useFactory: ( - cryptoService: CryptoService, - i18nService: I18nServiceAbstraction, - keyGenerationService: KeyGenerationService, - stateServiceAbstraction: StateServiceAbstraction, - ) => { - return new BrowserSendService( - cryptoService, - i18nService, - keyGenerationService, - stateServiceAbstraction, - ); - }, - deps: [CryptoService, I18nServiceAbstraction, KeyGenerationService, StateServiceAbstraction], - }, - { - provide: InternalSendServiceAbstraction, - useExisting: SendService, - }, - { - provide: SendApiServiceAbstraction, - useFactory: ( - apiService: ApiService, - fileUploadService: FileUploadService, - sendService: InternalSendServiceAbstraction, - ) => { - return new SendApiService(apiService, fileUploadService, sendService); - }, - deps: [ApiService, FileUploadService, InternalSendServiceAbstraction], - }, - { provide: SyncService, useFactory: getBgService("syncService"), deps: [] }, - { - provide: DomainSettingsService, - useClass: DefaultDomainSettingsService, - deps: [StateProvider], - }, - { - provide: AbstractStorageService, - useClass: BrowserLocalStorageService, - deps: [], - }, - { - provide: AutofillService, - useFactory: getBgService("autofillService"), - deps: [], - }, - { - provide: ImportServiceAbstraction, - useFactory: getBgService("importService"), - deps: [], - }, - { - provide: VaultExportServiceAbstraction, - useFactory: getBgService("exportService"), - deps: [], - }, - { - provide: KeyConnectorService, - useFactory: getBgService("keyConnectorService"), - deps: [], - }, - { - provide: UserVerificationService, - useFactory: getBgService("userVerificationService"), - deps: [], - }, - { - provide: VaultTimeoutSettingsService, - useFactory: getBgService("vaultTimeoutSettingsService"), - deps: [], - }, - { - provide: VaultTimeoutService, - useFactory: getBgService("vaultTimeoutService"), - deps: [], - }, - { - provide: NotificationsService, - useFactory: getBgService("notificationsService"), - deps: [], - }, - { - provide: VaultFilterService, - useClass: VaultFilterService, - deps: [ - OrganizationService, - FolderServiceAbstraction, - CipherService, - CollectionService, - PolicyService, - StateProvider, - AccountServiceAbstraction, - ], - }, - { - provide: SECURE_STORAGE, - useExisting: AbstractStorageService, // Secure storage is not available in the browser, so we use normal storage instead and warn users when it is used. - }, - { - provide: MEMORY_STORAGE, - useFactory: getBgService("memoryStorageService"), - }, - { - provide: OBSERVABLE_MEMORY_STORAGE, - useClass: ForegroundMemoryStorageService, - deps: [], - }, - { - provide: OBSERVABLE_DISK_STORAGE, - useExisting: AbstractStorageService, - }, - { - provide: StateServiceAbstraction, - useFactory: ( - storageService: AbstractStorageService, - secureStorageService: AbstractStorageService, - memoryStorageService: AbstractMemoryStorageService, - logService: LogServiceAbstraction, - accountService: AccountServiceAbstraction, - environmentService: EnvironmentService, - tokenService: TokenService, - migrationRunner: MigrationRunner, - ) => { - return new BrowserStateService( - storageService, - secureStorageService, - memoryStorageService, - logService, - new StateFactory(GlobalState, Account), - accountService, - environmentService, - tokenService, - migrationRunner, - ); - }, - deps: [ - AbstractStorageService, - SECURE_STORAGE, - MEMORY_STORAGE, - LogServiceAbstraction, - AccountServiceAbstraction, - EnvironmentService, - TokenService, - MigrationRunner, - ], - }, - { - provide: UsernameGenerationServiceAbstraction, - useFactory: getBgService("usernameGenerationService"), - deps: [], - }, - { - provide: BaseStateServiceAbstraction, - useExisting: StateServiceAbstraction, - deps: [], - }, - { - provide: FileDownloadService, - useClass: BrowserFileDownloadService, - }, - { - provide: LoginServiceAbstraction, - useClass: LoginService, - deps: [StateServiceAbstraction], - }, - { - provide: SYSTEM_THEME_OBSERVABLE, - useFactory: (platformUtilsService: PlatformUtilsService) => { - // Safari doesn't properly handle the (prefers-color-scheme) media query in the popup window, it always returns light. - // In Safari, we have to use the background page instead, which comes with limitations like not dynamically changing the extension theme when the system theme is changed. - let windowContext = window; - const backgroundWindow = BrowserApi.getBackgroundPage(); - if (platformUtilsService.isSafari() && backgroundWindow) { - windowContext = backgroundWindow; - } - - return AngularThemingService.createSystemThemeFromWindow(windowContext); - }, - deps: [PlatformUtilsService], - }, - { - provide: ConfigService, - useClass: BrowserConfigService, - deps: [ - StateServiceAbstraction, - ConfigApiServiceAbstraction, - AuthServiceAbstraction, - EnvironmentService, - LogService, - ], - }, - { - provide: FilePopoutUtilsService, - useFactory: (platformUtilsService: PlatformUtilsService) => { - return new FilePopoutUtilsService(platformUtilsService); - }, - deps: [PlatformUtilsService], - }, - { - provide: DerivedStateProvider, - useClass: ForegroundDerivedStateProvider, - deps: [OBSERVABLE_MEMORY_STORAGE, NgZone], - }, - { - provide: AutofillSettingsServiceAbstraction, - useClass: AutofillSettingsService, - deps: [StateProvider, PolicyService], - }, - { - provide: UserNotificationSettingsServiceAbstraction, - useClass: UserNotificationSettingsService, - deps: [StateProvider], - }, - ], + // Do not register your dependency here! Add it to the typesafeProviders array using the helper function + providers: safeProviders, }) export class ServicesModule {} diff --git a/apps/browser/src/popup/settings/about.component.html b/apps/browser/src/popup/settings/about.component.html index b408617834..a4ad0ba801 100644 --- a/apps/browser/src/popup/settings/about.component.html +++ b/apps/browser/src/popup/settings/about.component.html @@ -6,33 +6,33 @@

© Bitwarden Inc. 2015-{{ year }}

{{ "version" | i18n }}: {{ version }}

- -

- {{ "serverVersion" | i18n }}: {{ this.serverConfig?.version }} - - ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }}) + +

+ {{ "serverVersion" | i18n }}: {{ data.serverConfig?.version }} + + ({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})

- - + +

{{ "serverVersion" | i18n }} ({{ "thirdParty" | i18n }}): - {{ this.serverConfig?.version }} - - ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }}) + {{ data.serverConfig?.version }} + + ({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})

- {{ "thirdPartyServerMessage" | i18n: serverConfig.server?.name }} + {{ "thirdPartyServerMessage" | i18n: data.serverConfig.server?.name }}
-

+

{{ "serverVersion" | i18n }} ({{ "selfHostedServer" | i18n }}): - {{ this.serverConfig?.version }} - - ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }}) + {{ data.serverConfig?.version }} + + ({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})

diff --git a/apps/browser/src/popup/settings/about.component.ts b/apps/browser/src/popup/settings/about.component.ts index d6f6f000b6..61b5749b51 100644 --- a/apps/browser/src/popup/settings/about.component.ts +++ b/apps/browser/src/popup/settings/about.component.ts @@ -1,10 +1,9 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { Observable } from "rxjs"; +import { combineLatest, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; -import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { ButtonModule, DialogModule } from "@bitwarden/components"; @@ -16,14 +15,16 @@ import { BrowserApi } from "../../platform/browser/browser-api"; imports: [CommonModule, JslibModule, DialogModule, ButtonModule], }) export class AboutComponent { - protected serverConfig$: Observable = this.configService.serverConfig$; - protected year = new Date().getFullYear(); protected version = BrowserApi.getApplicationVersion(); - protected isCloud = this.environmentService.isCloud(); + + protected data$ = combineLatest([ + this.configService.serverConfig$, + this.environmentService.environment$.pipe(map((env) => env.isCloud())), + ]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud }))); constructor( - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private environmentService: EnvironmentService, ) {} } diff --git a/apps/browser/src/popup/settings/settings.component.ts b/apps/browser/src/popup/settings/settings.component.ts index 05f633d424..52e44a2531 100644 --- a/apps/browser/src/popup/settings/settings.component.ts +++ b/apps/browser/src/popup/settings/settings.component.ts @@ -446,9 +446,8 @@ export class SettingsComponent implements OnInit { 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(this.environmentService.getWebVaultUrl()); + const env = await firstValueFrom(this.environmentService.environment$); + await BrowserApi.createNewTab(env.getWebVaultUrl()); } } @@ -479,10 +478,9 @@ export class SettingsComponent implements OnInit { } async webVault() { - const url = this.environmentService.getWebVaultUrl(); - // 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(url); + const env = await firstValueFrom(this.environmentService.environment$); + const url = env.getWebVaultUrl(); + await BrowserApi.createNewTab(url); } async import() { diff --git a/apps/browser/src/services/browser-send.service.ts b/apps/browser/src/services/browser-send.service.ts deleted file mode 100644 index 8a197444a9..0000000000 --- a/apps/browser/src/services/browser-send.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BehaviorSubject } from "rxjs"; - -import { Send } from "@bitwarden/common/tools/send/models/domain/send"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service"; - -import { browserSession, sessionSync } from "../platform/decorators/session-sync-observable"; - -@browserSession -export class BrowserSendService extends SendService { - @sessionSync({ initializer: Send.fromJSON, initializeAs: "array" }) - protected _sends: BehaviorSubject; - @sessionSync({ initializer: SendView.fromJSON, initializeAs: "array" }) - protected _sendViews: BehaviorSubject; -} 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 d3436099ef..858889b887 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.spec.ts @@ -4,7 +4,7 @@ import { firstValueFrom } 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 { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { Importer, ImportResult, ImportServiceAbstraction } from "@bitwarden/importer/core"; diff --git a/apps/browser/src/tools/background/fileless-importer.background.ts b/apps/browser/src/tools/background/fileless-importer.background.ts index 3ddc7bd1b7..57c2faa930 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.ts @@ -5,7 +5,7 @@ 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 { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +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"; @@ -55,7 +55,7 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface * @param syncService - Used to trigger a full sync after the import is completed. */ constructor( - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private authService: AuthService, private policyService: PolicyService, private notificationBackground: NotificationBackground, diff --git a/apps/browser/src/tools/popup/settings/export.component.html b/apps/browser/src/tools/popup/settings/export.component.html index db072d6b50..aae3584f6c 100644 --- a/apps/browser/src/tools/popup/settings/export.component.html +++ b/apps/browser/src/tools/popup/settings/export.component.html @@ -19,7 +19,7 @@ {{ "personalVaultExportPolicyInEffect" | i18n }} - +
diff --git a/apps/browser/src/tools/popup/settings/export.component.ts b/apps/browser/src/tools/popup/settings/export.component.ts index 70735b5184..b62ed4c517 100644 --- a/apps/browser/src/tools/popup/settings/export.component.ts +++ b/apps/browser/src/tools/popup/settings/export.component.ts @@ -2,7 +2,6 @@ import { Component } from "@angular/core"; import { UntypedFormBuilder } from "@angular/forms"; import { Router } from "@angular/router"; -import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/tools/export/components/export.component"; 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"; @@ -13,6 +12,7 @@ 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 { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; +import { ExportComponent as BaseExportComponent } from "@bitwarden/vault-export-ui"; @Component({ selector: "app-export", diff --git a/apps/browser/src/vault/fido2/content/content-script.ts b/apps/browser/src/vault/fido2/content/content-script.ts index e12c592262..c2fc862f55 100644 --- a/apps/browser/src/vault/fido2/content/content-script.ts +++ b/apps/browser/src/vault/fido2/content/content-script.ts @@ -138,6 +138,7 @@ async function run() { }); } -// 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 -run(); +// Only run the script if the document is an HTML document +if (document.contentType === "text/html") { + void run(); +} 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 index ecdeb9cda7..8ff448b6f7 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.html +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.html @@ -138,10 +138,20 @@ attr.aria-label="{{ 'typePasskey' | i18n }} {{ fido2CredentialCreationDateValue }}" >
-
- {{ "typePasskey" | i18n }} - {{ "dateCreated" | i18n }} - {{ cipher.login.fido2Credentials[0].creationDate | date: "short" }} +
+ +
+ {{ "typePasskey" | i18n }} + {{ "dateCreated" | i18n }} + {{ cipher.login.fido2Credentials[0].creationDate | date: "short" }} +
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 index 054a45f374..a566b054c0 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -11,7 +11,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve 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 { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +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"; @@ -68,7 +68,7 @@ export class AddEditComponent extends BaseAddEditComponent { sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, - configService: ConfigServiceAbstraction, + configService: ConfigService, ) { super( cipherService, diff --git a/apps/browser/src/vault/popup/components/vault/collections.component.ts b/apps/browser/src/vault/popup/components/vault/collections.component.ts index acbdab3685..c8f85a8b7a 100644 --- a/apps/browser/src/vault/popup/components/vault/collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault/collections.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router"; import { first } from "rxjs/operators"; 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 { 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"; @@ -21,11 +22,19 @@ export class CollectionsComponent extends BaseCollectionsComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, cipherService: CipherService, + organizationService: OrganizationService, private route: ActivatedRoute, private location: Location, logService: LogService, ) { - super(collectionService, platformUtilsService, i18nService, cipherService, logService); + super( + collectionService, + platformUtilsService, + i18nService, + cipherService, + organizationService, + logService, + ); } async ngOnInit() { 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 index c2817ed50a..d9cf6550fa 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts @@ -262,14 +262,6 @@ export class CurrentTabComponent implements OnInit, OnDestroy { this.hostname = Utils.getHostname(this.url); this.pageDetails = []; - // 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.tabSendMessage(this.tab, { - command: "collectPageDetails", - tab: this.tab, - sender: BroadcasterSubscriptionId, - }); - const otherTypes: CipherType[] = []; const dontShowCards = !(await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$)); const dontShowIdentities = !(await firstValueFrom( @@ -310,9 +302,18 @@ export class CurrentTabComponent implements OnInit, OnDestroy { } }); - this.loginCiphers = this.loginCiphers.sort((a, b) => - this.cipherService.sortCiphersByLastUsedThenName(a, b), - ); + if (this.loginCiphers.length) { + void BrowserApi.tabSendMessage(this.tab, { + command: "collectPageDetails", + tab: this.tab, + sender: BroadcasterSubscriptionId, + }); + + this.loginCiphers = this.loginCiphers.sort((a, b) => + this.cipherService.sortCiphersByLastUsedThenName(a, b), + ); + } + this.isLoading = this.loaded = true; } 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 index 5e7959b38f..2510e2f966 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts @@ -20,7 +20,7 @@ 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 { BrowserStateService } from "../../../../platform/services/abstractions/browser-state.service"; +import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; import { VaultFilterService } from "../../../services/vault-filter.service"; const ComponentId = "VaultComponent"; @@ -84,8 +84,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private searchService: SearchService, private location: Location, - private browserStateService: BrowserStateService, private vaultFilterService: VaultFilterService, + private vaultBrowserStateService: VaultBrowserStateService, ) { this.noFolderListSize = 100; } @@ -95,7 +95,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { this.showLeftHeader = !( BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() ); - await this.browserStateService.setBrowserVaultItemsComponentState(null); + 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. @@ -120,7 +120,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { 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.browserStateService.getBrowserGroupingComponentState(); + this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); if (this.state?.searchText) { this.searchText = this.state.searchText; } else if (params.searchText) { @@ -413,11 +413,11 @@ export class VaultFilterComponent implements OnInit, OnDestroy { collections: this.collections, deletedCount: this.deletedCount, }); - await this.browserStateService.setBrowserGroupingComponentState(this.state); + await this.vaultBrowserStateService.setBrowserGroupingsComponentState(this.state); } private async restoreState(): Promise { - this.state = await this.browserStateService.getBrowserGroupingComponentState(); + this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); if (this.state == null) { return false; } 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 index 96d5fe170b..abb810c04d 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts @@ -21,7 +21,7 @@ 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 { BrowserStateService } from "../../../../platform/services/abstractions/browser-state.service"; +import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; import { VaultFilterService } from "../../../services/vault-filter.service"; const ComponentId = "VaultItemsComponent"; @@ -59,7 +59,7 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn private ngZone: NgZone, private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef, - private stateService: BrowserStateService, + private stateService: VaultBrowserStateService, private i18nService: I18nService, private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService, diff --git a/apps/browser/src/vault/services/vault-browser-state.service.spec.ts b/apps/browser/src/vault/services/vault-browser-state.service.spec.ts new file mode 100644 index 0000000000..b9369aa826 --- /dev/null +++ b/apps/browser/src/vault/services/vault-browser-state.service.spec.ts @@ -0,0 +1,87 @@ +import { + FakeAccountService, + mockAccountServiceWith, +} from "@bitwarden/common/../spec/fake-account-service"; +import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; +import { Jsonify } from "type-fest"; + +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherType } from "@bitwarden/common/vault/enums"; + +import { BrowserComponentState } from "../../models/browserComponentState"; +import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; + +import { + VAULT_BROWSER_COMPONENT, + VAULT_BROWSER_GROUPINGS_COMPONENT, + VaultBrowserStateService, +} from "./vault-browser-state.service"; + +describe("Vault Browser State Service", () => { + let stateProvider: FakeStateProvider; + + let accountService: FakeAccountService; + let stateService: VaultBrowserStateService; + const mockUserId = Utils.newGuid() as UserId; + + beforeEach(() => { + accountService = mockAccountServiceWith(mockUserId); + stateProvider = new FakeStateProvider(accountService); + + stateService = new VaultBrowserStateService(stateProvider); + }); + + describe("getBrowserGroupingsComponentState", () => { + it("should return a BrowserGroupingsComponentState", async () => { + await stateService.setBrowserGroupingsComponentState(new BrowserGroupingsComponentState()); + + const actual = await stateService.getBrowserGroupingsComponentState(); + + expect(actual).toBeInstanceOf(BrowserGroupingsComponentState); + }); + + it("should deserialize BrowserGroupingsComponentState", () => { + const sut = VAULT_BROWSER_GROUPINGS_COMPONENT; + + const expectedState = { + deletedCount: 0, + collectionCounts: new Map(), + folderCounts: new Map(), + typeCounts: new Map(), + }; + + const result = sut.deserializer( + JSON.parse(JSON.stringify(expectedState)) as Jsonify, + ); + + expect(result).toEqual(expectedState); + }); + }); + + describe("getBrowserVaultItemsComponentState", () => { + it("should deserialize BrowserComponentState", () => { + const sut = VAULT_BROWSER_COMPONENT; + + const expectedState = { + scrollY: 0, + searchText: "test", + }; + + const result = sut.deserializer(JSON.parse(JSON.stringify(expectedState))); + + expect(result).toEqual(expectedState); + }); + + it("should return a BrowserComponentState", async () => { + const componentState = new BrowserComponentState(); + componentState.scrollY = 0; + componentState.searchText = "test"; + + await stateService.setBrowserVaultItemsComponentState(componentState); + + const actual = await stateService.getBrowserVaultItemsComponentState(); + expect(actual).toStrictEqual(componentState); + }); + }); +}); diff --git a/apps/browser/src/vault/services/vault-browser-state.service.ts b/apps/browser/src/vault/services/vault-browser-state.service.ts new file mode 100644 index 0000000000..a0d55a9d55 --- /dev/null +++ b/apps/browser/src/vault/services/vault-browser-state.service.ts @@ -0,0 +1,65 @@ +import { Observable, firstValueFrom } from "rxjs"; +import { Jsonify } from "type-fest"; + +import { + ActiveUserState, + KeyDefinition, + StateProvider, + VAULT_BROWSER_MEMORY, +} from "@bitwarden/common/platform/state"; + +import { BrowserComponentState } from "../../models/browserComponentState"; +import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; + +export const VAULT_BROWSER_GROUPINGS_COMPONENT = new KeyDefinition( + VAULT_BROWSER_MEMORY, + "vault_browser_groupings_component", + { + deserializer: (obj: Jsonify) => + BrowserGroupingsComponentState.fromJSON(obj), + }, +); + +export const VAULT_BROWSER_COMPONENT = new KeyDefinition( + VAULT_BROWSER_MEMORY, + "vault_browser_component", + { + deserializer: (obj: Jsonify) => BrowserComponentState.fromJSON(obj), + }, +); + +export class VaultBrowserStateService { + vaultBrowserGroupingsComponentState$: Observable; + vaultBrowserComponentState$: Observable; + + private activeUserVaultBrowserGroupingsComponentState: ActiveUserState; + private activeUserVaultBrowserComponentState: ActiveUserState; + + constructor(protected stateProvider: StateProvider) { + this.activeUserVaultBrowserGroupingsComponentState = this.stateProvider.getActive( + VAULT_BROWSER_GROUPINGS_COMPONENT, + ); + this.activeUserVaultBrowserComponentState = + this.stateProvider.getActive(VAULT_BROWSER_COMPONENT); + + this.vaultBrowserGroupingsComponentState$ = + this.activeUserVaultBrowserGroupingsComponentState.state$; + this.vaultBrowserComponentState$ = this.activeUserVaultBrowserComponentState.state$; + } + + async getBrowserGroupingsComponentState(): Promise { + return await firstValueFrom(this.vaultBrowserGroupingsComponentState$); + } + + async setBrowserGroupingsComponentState(value: BrowserGroupingsComponentState): Promise { + await this.activeUserVaultBrowserGroupingsComponentState.update(() => value); + } + + async getBrowserVaultItemsComponentState(): Promise { + return await firstValueFrom(this.vaultBrowserComponentState$); + } + + async setBrowserVaultItemsComponentState(value: BrowserComponentState): Promise { + await this.activeUserVaultBrowserComponentState.update(() => value); + } +} diff --git a/apps/browser/store/locales/gl/copy.resx b/apps/browser/store/locales/gl/copy.resx index 191198691d..d812256fb7 100644 --- a/apps/browser/store/locales/gl/copy.resx +++ b/apps/browser/store/locales/gl/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden – Free Password Manager + Bitwarden – Xestor de contrasinais gratuíto - A secure and free password manager for all of your devices + Un xestor de contrasinais seguro e gratuíto para todos os teus dispositivos - Bitwarden, Inc. is the parent company of 8bit Solutions LLC. + Bitwarden, Inc. é a empresa matriz de 8bit Solutions LLC. -NAMED BEST PASSWORD MANAGER BY THE VERGE, U.S. NEWS & WORLD REPORT, CNET, AND MORE. +NOMEADO MELLOR ADMINISTRADOR DE CONTRASINAIS POR THE VERGE, Ou.S. NEWS & WORLD REPORT, CNET E MÁS. -Manage, store, secure, and share unlimited passwords across unlimited devices from anywhere. Bitwarden delivers open source password management solutions to everyone, whether at home, at work, or on the go. +Administre, almacene, protexa e comparta contrasinais ilimitados en dispositivos ilimitados desde calquera lugar. Bitwarden ofrece solucións de xestión de contrasinais de código aberto para todos, xa sexa en casa, no traballo ou en mentres estás de viaxe. -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Xere contrasinais seguros, únicas e aleatorias en función dos requisitos de seguridade de cada sitio web que frecuenta. -Bitwarden Send quickly transmits encrypted information --- files and plaintext -- directly to anyone. +Bitwarden Send transmite rapidamente información cifrada --- arquivos e texto sen formato, directamente a calquera persoa. -Bitwarden offers Teams and Enterprise plans for companies so you can securely share passwords with colleagues. +Bitwarden ofrece plans Teams e Enterprise para empresas para que poida compartir contrasinais de forma segura con colegas. -Why Choose Bitwarden: +Por que elixir Bitwarden? -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashing, and PBKDF2 SHA-256) so your data stays secure and private. +Cifrado de clase mundial +Os contrasinais están protexidas con cifrado avanzado de extremo a extremo (AES-256 bits, salted hashing e PBKDF2 XA-256) para que os seus datos permanezan seguros e privados. -Built-in Password Generator -Generate strong, unique, and random passwords based on security requirements for every website you frequent. +Xerador de contrasinais incorporado +Xere contrasinais fortes, únicas e aleatorias en función dos requisitos de seguridade de cada sitio web que frecuenta. -Global Translations -Bitwarden translations exist in 40 languages and are growing, thanks to our global community. +Traducións Globais +As traducións de Bitwarden existen en 40 idiomas e están a crecer, grazas á nosa comunidade global. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Aplicacións multiplataforma +Protexa e comparta datos confidenciais dentro da súa Caixa Forte de Bitwarden desde calquera navegador, dispositivo móbil ou sistema operativo de escritorio, e máis. - A secure and free password manager for all of your devices + Un xestor de contrasinais seguro e gratuíto para todos os teus dispositivos - Sync and access your vault from multiple devices + Sincroniza e accede á túa caixa forte desde múltiples dispositivos - Manage all your logins and passwords from a secure vault + Xestiona todos os teus usuarios e contrasinais desde unha caixa forte segura - Quickly auto-fill your login credentials into any website that you visit + Autocompleta rapidamente os teus datos de acceso en calquera páxina web que visites - Your vault is also conveniently accessible from the right-click menu + A túa caixa forte tamén é facilmente accesible desde o menú de clic dereito - Automatically generate strong, random, and secure passwords + Xera automaticamente contrasinais fortes, aleatorias e seguras - Your information is managed securely using AES-256 bit encryption + A túa información é xestionada de forma segura con cifrado AES de 256 bits diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index a4176be0b0..694246f59a 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -20,6 +20,7 @@ "@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"], diff --git a/apps/cli/package.json b/apps/cli/package.json index 2873be4242..690842d831 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": "2024.3.0", + "version": "2024.3.1", "keywords": [ "bitwarden", "password", @@ -71,7 +71,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.13", + "tldts": "6.1.16", "zxcvbn": "4.4.2" } } diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 97c0974ac6..a91e876e92 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -690,6 +690,8 @@ export class LoginCommand { codeChallenge: string, state: string, ): Promise<{ ssoCode: string; orgIdentifier: string }> { + const env = await firstValueFrom(this.environmentService.environment$); + return new Promise((resolve, reject) => { const callbackServer = http.createServer((req, res) => { const urlString = "http://localhost" + req.url; @@ -724,7 +726,7 @@ export class LoginCommand { } }); let foundPort = false; - const webUrl = this.environmentService.getWebVaultUrl(); + const webUrl = env.getWebVaultUrl(); for (let port = 8065; port <= 8070; port++) { try { this.ssoRedirectUri = "http://localhost:" + port; diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index d383545d38..ad215f7b54 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -5,11 +5,13 @@ import { program } from "commander"; import * as jsdom from "jsdom"; import { + InternalUserDecryptionOptionsServiceAbstraction, AuthRequestService, LoginStrategyService, LoginStrategyServiceAbstraction, PinCryptoService, PinCryptoServiceAbstraction, + UserDecryptionOptionsService, } from "@bitwarden/auth/common"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -45,6 +47,8 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { BiometricStateService, @@ -57,10 +61,11 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/services/broadcaster.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; +import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { CryptoService } from "@bitwarden/common/platform/services/crypto.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; @@ -128,7 +133,6 @@ import { VaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; -import { CliConfigService } from "./platform/services/cli-config.service"; import { CliPlatformUtilsService } from "./platform/services/cli-platform-utils.service"; import { ConsoleLogService } from "./platform/services/console-log.service"; import { I18nService } from "./platform/services/i18n.service"; @@ -169,6 +173,7 @@ export class Main { eventUploadService: EventUploadServiceAbstraction; passwordGenerationService: PasswordGenerationServiceAbstraction; passwordStrengthService: PasswordStrengthServiceAbstraction; + userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction; totpService: TotpService; containerService: ContainerService; auditService: AuditService; @@ -210,7 +215,7 @@ export class Main { deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction; authRequestService: AuthRequestService; configApiService: ConfigApiServiceAbstraction; - configService: CliConfigService; + configService: ConfigService; accountService: AccountService; globalStateProvider: GlobalStateProvider; singleUserStateProvider: SingleUserStateProvider; @@ -309,13 +314,21 @@ export class Main { this.derivedStateProvider, ); - this.environmentService = new EnvironmentService(this.stateProvider, this.accountService); + this.environmentService = new DefaultEnvironmentService( + this.stateProvider, + this.accountService, + ); + + this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService); this.tokenService = new TokenService( this.singleUserStateProvider, this.globalStateProvider, this.platformUtilsService.supportsSecureStorage(), this.secureStorageService, + this.keyGenerationService, + this.encryptService, + this.logService, ); const migrationRunner = new MigrationRunner( @@ -336,8 +349,6 @@ export class Main { migrationRunner, ); - this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService); - this.cryptoService = new CryptoService( this.keyGenerationService, this.cryptoFunctionService, @@ -416,7 +427,6 @@ export class Main { this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.keyConnectorService = new KeyConnectorService( - this.stateService, this.cryptoService, this.apiService, this.tokenService, @@ -424,6 +434,7 @@ export class Main { this.organizationService, this.keyGenerationService, async (expired: boolean) => await this.logout(), + this.stateProvider, ); this.twoFactorService = new TwoFactorService(this.i18nService, this.platformUtilsService); @@ -436,17 +447,21 @@ export class Main { this.stateService, ); + this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); + this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.deviceTrustCryptoService = new DeviceTrustCryptoService( this.keyGenerationService, this.cryptoFunctionService, this.cryptoService, this.encryptService, - this.stateService, this.appIdService, this.devicesApiService, this.i18nService, this.platformUtilsService, + this.stateProvider, + this.secureStorageService, + this.userDecryptionOptionsService, ); this.authRequestService = new AuthRequestService( @@ -457,7 +472,7 @@ export class Main { ); this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService( - this.activeUserStateProvider, + this.stateProvider, ); this.loginStrategyService = new LoginStrategyService( @@ -478,26 +493,27 @@ export class Main { this.policyService, this.deviceTrustCryptoService, this.authRequestService, + this.userDecryptionOptionsService, this.globalStateProvider, this.billingAccountProfileStateService, ); this.authService = new AuthService( + this.accountService, this.messagingService, this.cryptoService, this.apiService, this.stateService, + this.tokenService, ); - this.configApiService = new ConfigApiService(this.apiService, this.authService); + this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - this.configService = new CliConfigService( - this.stateService, + this.configService = new DefaultConfigService( this.configApiService, - this.authService, this.environmentService, this.logService, - true, + this.stateProvider, ); this.cipherService = new CipherService( @@ -529,6 +545,7 @@ export class Main { this.biometricStateService = new DefaultBiometricStateService(this.stateProvider); this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( + this.userDecryptionOptionsService, this.cryptoService, this.tokenService, this.policyService, @@ -548,6 +565,7 @@ export class Main { this.cryptoService, this.i18nService, this.userVerificationApiService, + this.userDecryptionOptionsService, this.pinCryptoService, this.logService, this.vaultTimeoutSettingsService, @@ -589,6 +607,7 @@ export class Main { this.folderApiService, this.organizationService, this.sendApiService, + this.userDecryptionOptionsService, this.avatarService, async (expired: boolean) => await this.logout(), this.billingAccountProfileStateService, @@ -693,16 +712,8 @@ export class Main { await this.storageService.init(); await this.stateService.init(); this.containerService.attachToGlobal(global); - await this.environmentService.setUrlsFromStorage(); await this.i18nService.init(); this.twoFactorService.init(); - this.configService.init(); - - const installedVersion = await this.stateService.getInstalledVersion(); - const currentVersion = await this.platformUtilsService.getApplicationVersion(); - if (installedVersion == null || installedVersion !== currentVersion) { - await this.stateService.setInstalledVersion(currentVersion); - } } } diff --git a/apps/cli/src/commands/config.command.ts b/apps/cli/src/commands/config.command.ts index 209f75a836..eb6559443d 100644 --- a/apps/cli/src/commands/config.command.ts +++ b/apps/cli/src/commands/config.command.ts @@ -1,6 +1,10 @@ import { OptionValues } from "commander"; +import { firstValueFrom } from "rxjs"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { + EnvironmentService, + Region, +} from "@bitwarden/common/platform/abstractions/environment.service"; import { Response } from "../models/response"; import { MessageResponse } from "../models/response/message.response"; @@ -29,16 +33,15 @@ export class ConfigCommand { !options.notifications && !options.events ) { + const env = await firstValueFrom(this.environmentService.environment$); const stringRes = new StringResponse( - this.environmentService.hasBaseUrl() - ? this.environmentService.getUrls().base - : "https://bitwarden.com", + env.hasBaseUrl() ? env.getUrls().base : "https://bitwarden.com", ); return Response.success(stringRes); } url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url; - await this.environmentService.setUrls({ + await this.environmentService.setEnvironment(Region.SelfHosted, { base: url, webVault: options.webVault || null, api: options.api || null, diff --git a/apps/cli/src/commands/convert-to-key-connector.command.ts b/apps/cli/src/commands/convert-to-key-connector.command.ts index de9565dfb6..654606dc06 100644 --- a/apps/cli/src/commands/convert-to-key-connector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -1,8 +1,12 @@ import * as inquirer from "inquirer"; +import { firstValueFrom } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { + EnvironmentService, + Region, +} from "@bitwarden/common/platform/abstractions/environment.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { Response } from "../models/response"; @@ -67,9 +71,10 @@ export class ConvertToKeyConnectorCommand { await this.keyConnectorService.setUsesKeyConnector(true); // Update environment URL - required for api key login - const urls = this.environmentService.getUrls(); + const env = await firstValueFrom(this.environmentService.environment$); + const urls = env.getUrls(); urls.keyConnector = organization.keyConnectorUrl; - await this.environmentService.setUrls(urls); + await this.environmentService.setEnvironment(Region.SelfHosted, urls); return Response.success(); } else if (answer.convert === "leave") { diff --git a/apps/cli/src/commands/status.command.ts b/apps/cli/src/commands/status.command.ts index d7bc17b643..32b93a7e40 100644 --- a/apps/cli/src/commands/status.command.ts +++ b/apps/cli/src/commands/status.command.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -17,7 +19,7 @@ export class StatusCommand { async run(): Promise { try { - const baseUrl = this.baseUrl(); + const baseUrl = await this.baseUrl(); const status = await this.status(); const lastSync = await this.syncService.getLastSync(); const userId = await this.stateService.getUserId(); @@ -37,8 +39,9 @@ export class StatusCommand { } } - private baseUrl(): string { - return this.envService.getUrls().base; + private async baseUrl(): Promise { + const env = await firstValueFrom(this.envService.environment$); + return env.getUrls().base; } private async status(): Promise<"unauthenticated" | "locked" | "unlocked"> { diff --git a/apps/cli/src/platform/services/cli-config.service.ts b/apps/cli/src/platform/services/cli-config.service.ts deleted file mode 100644 index 6faa1b12e8..0000000000 --- a/apps/cli/src/platform/services/cli-config.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NEVER } from "rxjs"; - -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; - -export class CliConfigService extends ConfigService { - // The rxjs timer uses setTimeout/setInterval under the hood, which prevents the node process from exiting - // when the command is finished. Cli should never be alive long enough to use the timer, so we disable it. - protected refreshTimer$ = NEVER; -} diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts index 1f6f8059e1..a3f4b5c086 100644 --- a/apps/cli/src/tools/send/commands/create.command.ts +++ b/apps/cli/src/tools/send/commands/create.command.ts @@ -127,7 +127,8 @@ export class SendCreateCommand { await this.sendApiService.save([encSend, fileData]); const newSend = await this.sendService.getFromState(encSend.id); const decSend = await newSend.decrypt(); - const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl()); + const env = await firstValueFrom(this.environmentService.environment$); + const res = new SendResponse(decSend, env.getWebVaultUrl()); return Response.success(res); } catch (e) { return Response.error(e); diff --git a/apps/cli/src/tools/send/commands/get.command.ts b/apps/cli/src/tools/send/commands/get.command.ts index 9e80c350b7..2ffe242317 100644 --- a/apps/cli/src/tools/send/commands/get.command.ts +++ b/apps/cli/src/tools/send/commands/get.command.ts @@ -1,4 +1,5 @@ import { OptionValues } from "commander"; +import { firstValueFrom } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -32,7 +33,8 @@ export class SendGetCommand extends DownloadCommand { return Response.notFound(); } - const webVaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const webVaultUrl = env.getWebVaultUrl(); let filter = (s: SendView) => true; let selector = async (s: SendView): Promise => Response.success(new SendResponse(s, webVaultUrl)); diff --git a/apps/cli/src/tools/send/commands/list.command.ts b/apps/cli/src/tools/send/commands/list.command.ts index e169a26ea1..ab8a4dcb1c 100644 --- a/apps/cli/src/tools/send/commands/list.command.ts +++ b/apps/cli/src/tools/send/commands/list.command.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; @@ -21,7 +23,8 @@ export class SendListCommand { sends = this.searchService.searchSends(sends, normalizedOptions.search); } - const webVaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const webVaultUrl = env.getWebVaultUrl(); const res = new ListResponse(sends.map((s) => new SendResponse(s, webVaultUrl))); return Response.success(res); } diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index 8dfbf01fa9..dc662f0272 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -1,5 +1,6 @@ import { OptionValues } from "commander"; import * as inquirer from "inquirer"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -46,7 +47,7 @@ export class SendReceiveCommand extends DownloadCommand { return Response.badRequest("Failed to parse the provided Send url"); } - const apiUrl = this.getApiUrl(urlObject); + const apiUrl = await this.getApiUrl(urlObject); const [id, key] = this.getIdAndKey(urlObject); if (Utils.isNullOrWhitespace(id) || Utils.isNullOrWhitespace(key)) { @@ -108,8 +109,9 @@ export class SendReceiveCommand extends DownloadCommand { return [result[0], result[1]]; } - private getApiUrl(url: URL) { - const urls = this.environmentService.getUrls(); + private async getApiUrl(url: URL) { + const env = await firstValueFrom(this.environmentService.environment$); + const urls = env.getUrls(); if (url.origin === "https://send.bitwarden.com") { return "https://api.bitwarden.com"; } else if (url.origin === urls.api) { diff --git a/apps/cli/src/tools/send/commands/remove-password.command.ts b/apps/cli/src/tools/send/commands/remove-password.command.ts index a25ca4c07e..1c7289bf08 100644 --- a/apps/cli/src/tools/send/commands/remove-password.command.ts +++ b/apps/cli/src/tools/send/commands/remove-password.command.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { SendService } from "@bitwarden/common/tools/send/services//send.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; @@ -18,7 +20,8 @@ export class SendRemovePasswordCommand { const updatedSend = await this.sendService.get(id); const decSend = await updatedSend.decrypt(); - const webVaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const webVaultUrl = env.getWebVaultUrl(); const res = new SendResponse(decSend, webVaultUrl); return Response.success(res); } catch (e) { diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 138394886d..446bce87a0 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -45,9 +45,9 @@ checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arboard" -version = "3.3.0" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08" +checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" dependencies = [ "clipboard-win", "log", @@ -56,7 +56,6 @@ dependencies = [ "objc_id", "parking_lot", "thiserror", - "winapi", "wl-clipboard-rs", "x11rb", ] @@ -84,9 +83,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" [[package]] name = "bitflags" @@ -176,13 +175,11 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "4.5.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee" dependencies = [ "error-code", - "str-buf", - "winapi", ] [[package]] @@ -375,13 +372,9 @@ dependencies = [ [[package]] name = "error-code" -version = "2.3.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" -dependencies = [ - "libc", - "str-buf", -] +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" [[package]] name = "fastrand" @@ -476,12 +469,12 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.3.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ "libc", - "winapi", + "windows-targets 0.48.5", ] [[package]] @@ -529,7 +522,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -661,12 +654,12 @@ checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "winapi", + "windows-targets 0.52.4", ] [[package]] @@ -767,9 +760,9 @@ dependencies = [ [[package]] name = "napi" -version = "2.13.3" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd063c93b900149304e3ba96ce5bf210cd4f81ef5eb80ded0d100df3e85a3ac0" +checksum = "54a63d0570e4c3e0daf7a8d380563610e159f538e20448d6c911337246f40e84" dependencies = [ "bitflags 2.4.1", "ctor", @@ -781,29 +774,29 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" +checksum = "2f9130fccc5f763cf2069b34a089a18f0d0883c66aceb81f2fad541a3d823c43" [[package]] name = "napi-derive" -version = "2.13.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" +checksum = "05bb7c37e3c1dda9312fdbe4a9fc7507fca72288ba154ec093e2d49114e727ce" dependencies = [ "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "napi-derive-backend" -version = "1.0.52" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" +checksum = "f785a8b8d7b83e925f5aa6d2ae3c159d17fe137ac368dc185bef410e7acdaeb4" dependencies = [ "convert_case", "once_cell", @@ -811,14 +804,14 @@ dependencies = [ "quote", "regex", "semver", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "napi-sys" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" +checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b" dependencies = [ "libloading", ] @@ -896,9 +889,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_pipe" @@ -1201,12 +1194,6 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" -[[package]] -name = "str-buf" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" - [[package]] name = "syn" version = "1.0.109" @@ -1506,15 +1493,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "winapi-wsapoll" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1704,22 +1682,17 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ "gethostname", - "nix", - "winapi", - "winapi-wsapoll", + "rustix", "x11rb-protocol", ] [[package]] name = "x11rb-protocol" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" -dependencies = [ - "nix", -] +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 2542e4d214..a1625020e5 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -15,11 +15,11 @@ manual_test = [] [dependencies] aes = "=0.8.4" anyhow = "=1.0.80" -arboard = { version = "=3.3.0", default-features = false, features = ["wayland-data-control"] } -base64 = "=0.21.5" +arboard = { version = "=3.3.2", default-features = false, features = ["wayland-data-control"] } +base64 = "=0.22.0" cbc = { version = "=0.1.2", features = ["alloc"] } -napi = { version = "=2.13.3", features = ["async"] } -napi-derive = "=2.13.0" +napi = { version = "=2.16.0", features = ["async"] } +napi-derive = "=2.16.0" rand = "=0.8.5" retry = "=2.0.0" scopeguard = "=1.2.0" @@ -28,7 +28,7 @@ thiserror = "=1.0.51" typenum = "=1.17.0" [build-dependencies] -napi-build = "=2.0.1" +napi-build = "=2.1.2" [target.'cfg(windows)'.dependencies] widestring = "=1.0.2" diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index afc51ef9fe..5ce5ef948f 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -24,7 +24,7 @@ "**/node_modules/argon2/package.json", "**/node_modules/argon2/lib/binding/napi-v3/argon2.node" ], - "electronVersion": "28.2.7", + "electronVersion": "28.2.8", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", @@ -150,7 +150,69 @@ "identityName": "8bitSolutionsLLC.bitwardendesktop", "publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418", "publisherDisplayName": "8bit Solutions LLC", - "languages": ["en-US"] + "languages": [ + "en-US", + "af", + "ar", + "az-latn", + "be", + "bg", + "bn", + "bs", + "ca", + "cs", + "cy", + "da", + "de", + "el", + "en-gb", + "en-in", + "es", + "et", + "eu", + "fa", + "fi", + "fil", + "fr", + "gl", + "he", + "hi", + "hr", + "hu", + "id", + "it", + "ja", + "ka", + "km", + "kn", + "ko", + "lt", + "lv", + "ml", + "mr", + "nb", + "ne", + "nl", + "nn", + "or", + "pl", + "pt-br", + "pt-pt", + "ro", + "ru", + "si", + "sk", + "sl", + "sr", + "sv", + "te", + "th", + "tr", + "uk", + "vi", + "zh-cn", + "zh-tw" + ] }, "deb": { "artifactName": "${productName}-${version}-${arch}.${ext}", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 6b01918d6b..52dd0fafdb 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": "2024.3.1", + "version": "2024.3.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 7a61a55ccf..ae0409cdbc 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -30,7 +30,7 @@ - + {{ "vaultTimeoutPolicyWithActionInEffect" @@ -46,7 +46,7 @@ {{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }} - +
+
+
+ +
+ {{ + "enableHardwareAccelerationDesc" | i18n + }} +