diff --git a/.eslintrc.json b/.eslintrc.json index c606b8f933b..3fd6dec3d7e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -56,6 +56,7 @@ "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], "@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }], + "@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }], "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], "no-console": "error", "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package. @@ -141,8 +142,7 @@ } ] } - ], - "no-restricted-imports": ["error", { "patterns": ["src/**/*"] }] + ] } }, { @@ -164,147 +164,6 @@ "tailwindcss/no-contradicting-classname": "error" } }, - { - "files": ["libs/admin-console/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/admin-console/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/angular/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/angular/*", "src/**/*"] }] - } - }, - { - "files": ["libs/auth/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/auth/*", "src/**/*"] }] - } - }, - { - "files": ["libs/billing/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/billing/*", "src/**/*"] }] - } - }, - { - "files": ["libs/common/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/common/*", "src/**/*"] }] - } - }, - { - "files": ["libs/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/components/*", "src/**/*", "@bitwarden/angular/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/components/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-components/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/history/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-history/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/legacy/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-legacy/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/generator/extensions/navigation/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/generator-navigation/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-core/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-core/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/tools/export/vault-export/vault-export-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/vault-export-ui/*", "src/**/*"] } - ] - } - }, - { - "files": ["libs/importer/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/importer/*", "src/**/*"] }] - } - }, - { - "files": ["libs/node/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/node/*", "src/**/*"] }] - } - }, - { - "files": ["libs/platform/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/platform/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/send/send-ui/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/send-ui/*", "src/**/*"] }] - } - }, - { - "files": ["libs/tools/card/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/tools-card/*", "src/**/*"] }] - } - }, - { - "files": ["libs/vault/src/**/*.ts"], - "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/vault/*", "src/**/*"] }] - } - }, { "files": ["apps/browser/src/**/*.ts", "libs/**/*.ts"], "excludedFiles": [ @@ -344,10 +203,30 @@ ] } }, + { + "files": ["**/src/**/*.ts"], + "excludedFiles": ["**/platform/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + "**/platform/**/internal", // General internal pattern + // All features that have been converted to barrel files + "**/platform/messaging/**", + "**/src/**/*" // Prevent relative imports across libs. + ] + } + ] + } + }, { "files": ["bitwarden_license/bit-common/src/**/*.ts"], "rules": { - "no-restricted-imports": ["error", { "patterns": ["@bitwarden/bit-common/*", "src/**/*"] }] + "no-restricted-imports": [ + "error", + { "patterns": ["@bitwarden/bit-common/*", "**/src/**/*"] } + ] } }, { @@ -357,7 +236,12 @@ "no-restricted-imports": [ "error", { - "patterns": ["biwarden_license/**", "@bitwarden/bit-common/*", "@bitwarden/bit-web/*"] + "patterns": [ + "biwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + "**/src/**/*" + ] } ], // Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded diff --git a/.github/renovate.json b/.github/renovate.json index 76a52136ae7..a1987ca038d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -104,6 +104,8 @@ "matchPackageNames": [ "@babel/core", "@babel/preset-env", + "@bitwarden/sdk-internal", + "@electron/fuses", "@electron/notarize", "@electron/rebuild", "@ngtools/webpack", @@ -115,7 +117,7 @@ "@types/node", "@types/node-forge", "@types/node-ipc", - "@yao-pkg", + "@yao-pkg/pkg", "babel-loader", "browserslist", "copy-webpack-plugin", @@ -135,6 +137,7 @@ "tsconfig-paths-webpack-plugin", "type-fest", "typescript", + "typescript-strict-plugin", "webpack", "webpack-cli", "webpack-dev-server", @@ -151,12 +154,13 @@ "@angular/cdk", "@angular/cli", "@angular/common", - "@angular/compiler", "@angular/compiler-cli", + "@angular/compiler", "@angular/core", "@angular/forms", + "@angular/platform-browser-dynamic", + "@angular/platform-browser", "@angular/platform", - "@angular/compiler", "@angular/router", "@compodoc/compodoc", "@ng-select/ng-select", @@ -164,8 +168,11 @@ "@storybook/addon-actions", "@storybook/addon-designs", "@storybook/addon-essentials", + "@storybook/addon-interactions", "@storybook/addon-links", "@storybook/angular", + "@storybook/manager-api", + "@storybook/theming", "@types/react", "autoprefixer", "bootstrap", @@ -188,7 +195,9 @@ "matchPackageNames": [ "@angular-eslint/eslint-plugin", "@angular-eslint/eslint-plugin-template", + "@angular-eslint/schematics", "@angular-eslint/template-parser", + "@angular/elements", "@types/jest", "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", @@ -204,6 +213,7 @@ "jest-junit", "jest-mock-extended", "jest-preset-angular", + "jest-diff", "lint-staged", "ts-jest" ], diff --git a/.github/workflows/brew-bump-desktop.yml b/.github/workflows/brew-bump-desktop.yml deleted file mode 100644 index 1b3c99128bf..00000000000 --- a/.github/workflows/brew-bump-desktop.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Bump Desktop Cask - -on: - push: - tags: - - desktop-v** - workflow_dispatch: - -defaults: - run: - shell: bash - -jobs: - update-desktop-cask: - name: Update Bitwarden Desktop Cask - runs-on: macos-13 - steps: - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "brew-bump-workflow-pat" - - - name: Update Homebrew cask - uses: macauley/action-homebrew-bump-cask@445c42390d790569d938f9068d01af39ca030feb # v1.0.0 - with: - # Required, custom GitHub access token with the 'public_repo' and 'workflow' scopes - token: ${{ steps.retrieve-secrets.outputs.brew-bump-workflow-pat }} - org: bitwarden - tap: Homebrew/homebrew-cask - cask: bitwarden - tag: ${{ github.ref }} - revision: ${{ github.sha }} - force: true - dryrun: true diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index aa62d602ad8..64cbaa0c7f1 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -196,11 +196,7 @@ jobs: - name: "edge" npm_command: "dist:edge" archive_name: "dist-edge.zip" - artifact_name: "dist-edge" - - name: "edge-mv3" - npm_command: "dist:edge:mv3" - archive_name: "dist-edge.zip" - artifact_name: "DO-NOT-USE-FOR-PROD-dist-edge-MV3" + artifact_name: "dist-edge-MV3" - name: "firefox" npm_command: "dist:firefox" archive_name: "dist-firefox.zip" @@ -212,11 +208,7 @@ jobs: - name: "opera" npm_command: "dist:opera" archive_name: "dist-opera.zip" - artifact_name: "dist-opera" - - name: "opera-mv3" - npm_command: "dist:opera:mv3" - archive_name: "dist-opera.zip" - artifact_name: "DO-NOT-USE-FOR-PROD-dist-opera-MV3" + artifact_name: "dist-opera-MV3" steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 3221c7eef2f..b27d1486bd2 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1196,6 +1196,8 @@ jobs: uses: slackapi/slack-github-action@485a9d42d3a73031f12ec201c457e2162c45d02d # v2.0.0 with: channel-id: C074F5UESQ0 + method: chat.postMessage + token: ${{ steps.retrieve-slack-secret.outputs.slack-bot-token }} payload: | { "blocks": [ @@ -1209,7 +1211,6 @@ jobs: ] } env: - SLACK_BOT_TOKEN: ${{ steps.retrieve-slack-secret.outputs.slack-bot-token }} BUILD_NUMBER: ${{ needs.setup.outputs.build_number }} diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index b5e84ff875b..9b890491282 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -63,14 +63,14 @@ jobs: runs-on: ubuntu-22.04 outputs: environment: ${{ steps.config.outputs.environment }} - environment-url: ${{ steps.config.outputs.environment-url }} - environment-name: ${{ steps.config.outputs.environment-name }} - environment-artifact: ${{ steps.config.outputs.environment-artifact }} - azure-login-creds: ${{ steps.config.outputs.azure-login-creds }} - retrieve-secrets-keyvault: ${{ steps.config.outputs.retrieve-secrets-keyvault }} - sync-utility: ${{ steps.config.outputs.sync-utility }} - sync-delete-destination-files: ${{ steps.config.outputs.sync-delete-destination-files }} - slack-channel-name: ${{ steps.config.outputs.slack-channel-name }} + environment_url: ${{ steps.config.outputs.environment_url }} + environment_name: ${{ steps.config.outputs.environment_name }} + environment_artifact: ${{ steps.config.outputs.environment_artifact }} + azure_login_creds: ${{ steps.config.outputs.azure_login_creds }} + retrive_secrets_keyvault: ${{ steps.config.outputs.retrive_secrets_keyvault }} + sync_utility: ${{ steps.config.outputs.sync_utility }} + sync_delete_destination_files: ${{ steps.config.outputs.sync_delete_destination_files }} + slack_channel_name: ${{ steps.config.outputs.slack_channel_name }} steps: - name: Configure id: config @@ -81,48 +81,48 @@ jobs: case ${{ inputs.environment }} in "USQA") - echo "azure-login-creds=AZURE_KV_US_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=bw-webvault-rlktusqa-kv" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-QA.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - US QA Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-qa" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_US_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=bw-webvault-rlktusqa-kv" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-QA.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - US QA Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-qa" >> $GITHUB_OUTPUT ;; "EUQA") - echo "azure-login-creds=AZURE_KV_EU_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=webvaulteu-westeurope-qa" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-euqa.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - EU QA Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-qa" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_EU_QA_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=webvaulteu-westeurope-qa" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-euqa.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - EU QA Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-qa" >> $GITHUB_OUTPUT ;; "USPROD") - echo "azure-login-creds=AZURE_KV_US_PROD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=bw-webvault-klrt-kv" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-COMMERCIAL.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - US Production Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.bitwarden.com" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-prd" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_US_PROD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=bw-webvault-klrt-kv" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-COMMERCIAL.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - US Production Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.bitwarden.com" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-prd" >> $GITHUB_OUTPUT ;; "EUPROD") - echo "azure-login-creds=AZURE_KV_EU_PRD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=webvault-westeurope-prod" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-euprd.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - EU Production Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.bitwarden.eu" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-prd" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_EU_PRD_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=webvault-westeurope-prod" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-euprd.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - EU Production Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.bitwarden.eu" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-prd" >> $GITHUB_OUTPUT ;; "USDEV") - echo "azure-login-creds=AZURE_KV_US_DEV_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT - echo "retrieve-secrets-keyvault=webvault-eastus-dev" >> $GITHUB_OUTPUT - echo "environment-artifact=web-*-cloud-usdev.zip" >> $GITHUB_OUTPUT - echo "environment-name=Web Vault - US Development Cloud" >> $GITHUB_OUTPUT - echo "environment-url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT - echo "slack-channel-name=alerts-deploy-dev" >> $GITHUB_OUTPUT + echo "azure_login_creds=AZURE_KV_US_DEV_SERVICE_PRINCIPAL" >> $GITHUB_OUTPUT + echo "retrive_secrets_keyvault=webvault-eastus-dev" >> $GITHUB_OUTPUT + echo "environment_artifact=web-*-cloud-usdev.zip" >> $GITHUB_OUTPUT + echo "environment_name=Web Vault - US Development Cloud" >> $GITHUB_OUTPUT + echo "environment_url=http://vault.$ENV_NAME_LOWER.bitwarden.pw" >> $GITHUB_OUTPUT + echo "slack_channel_name=alerts-deploy-dev" >> $GITHUB_OUTPUT ;; esac # Set the sync utility to use for deployment to the environment (az-sync or azcopy) - echo "sync-utility=azcopy" >> $GITHUB_OUTPUT + echo "sync_utility=azcopy" >> $GITHUB_OUTPUT - name: Environment Protection env: @@ -168,10 +168,10 @@ jobs: fi approval: - name: Approval for Deployment to ${{ needs.setup.outputs.environment-name }} + name: Approval for Deployment to ${{ needs.setup.outputs.environment_name }} needs: setup runs-on: ubuntu-22.04 - environment: ${{ needs.setup.outputs.environment-name }} + environment: ${{ needs.setup.outputs.environment_name }} steps: - name: Success Code run: exit 0 @@ -181,9 +181,9 @@ jobs: runs-on: ubuntu-22.04 needs: setup env: - _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} + _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment_artifact }} outputs: - artifact-build-commit: ${{ steps.set-artifact-commit.outputs.commit }} + artifact_build_commit: ${{ steps.set-artifact-commit.outputs.commit }} steps: - name: 'Download latest cloud asset using GitHub Run ID: ${{ inputs.build-web-run-id }}' if: ${{ inputs.build-web-run-id }} @@ -242,7 +242,7 @@ jobs: run: | # If run-id was used, get the commit from the download-latest-artifacts-run-id step if [ "${{ inputs.build-web-run-id }}" ]; then - echo "commit=${{ steps.download-latest-artifacts-run-id.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + echo "commit=${{ steps.download-latest-artifacts-run-id.outputs.artifact_build_commit }}" >> $GITHUB_OUTPUT elif [ "${{ steps.download-latest-artifacts.outcome }}" == "failure" ]; then # If the download-latest-artifacts step failed, query the GH API to get the commit SHA of the artifact that was just built with trigger-build-web. @@ -251,7 +251,7 @@ jobs: else # Set the commit to the output of step download-latest-artifacts. - echo "commit=${{ steps.download-latest-artifacts.outputs.artifact-build-commit }}" >> $GITHUB_OUTPUT + echo "commit=${{ steps.download-latest-artifacts.outputs.artifact_build_commit }}" >> $GITHUB_OUTPUT fi notify-start: @@ -271,11 +271,11 @@ jobs: id: slack-message with: project: Clients - environment: ${{ needs.setup.outputs.environment-name }} + environment: ${{ needs.setup.outputs.environment_name }} tag: ${{ inputs.branch-or-tag }} - slack-channel: ${{ needs.setup.outputs.slack-channel-name }} + slack-channel: ${{ needs.setup.outputs.slack_channel_name }} event: 'start' - commit-sha: ${{ needs.artifact-check.outputs.artifact-build-commit }} + commit-sha: ${{ needs.artifact-check.outputs.artifact_build_commit }} url: https://github.com/bitwarden/clients/actions/runs/${{ github.run_id }} AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -287,7 +287,7 @@ jobs: - name: Display commit SHA run: | REPO_URL="https://github.com/bitwarden/clients/commit" - COMMIT_SHA="${{ needs.artifact-check.outputs.artifact-build-commit }}" + COMMIT_SHA="${{ needs.artifact-check.outputs.artifact_build_commit }}" echo ":steam_locomotive: View [commit]($REPO_URL/$COMMIT_SHA)" >> $GITHUB_STEP_SUMMARY azure-deploy: @@ -299,9 +299,9 @@ jobs: runs-on: ubuntu-22.04 env: _ENVIRONMENT: ${{ needs.setup.outputs.environment }} - _ENVIRONMENT_URL: ${{ needs.setup.outputs.environment-url }} - _ENVIRONMENT_NAME: ${{ needs.setup.outputs.environment-name }} - _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment-artifact }} + _ENVIRONMENT_URL: ${{ needs.setup.outputs.environment_url }} + _ENVIRONMENT_NAME: ${{ needs.setup.outputs.environment_name }} + _ENVIRONMENT_ARTIFACT: ${{ needs.setup.outputs.environment_artifact }} steps: - name: Create GitHub deployment uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 @@ -309,31 +309,31 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' - environment-url: ${{ env._ENVIRONMENT_URL }} + environment_url: ${{ env._ENVIRONMENT_URL }} environment: ${{ env._ENVIRONMENT_NAME }} task: 'deploy' description: 'Deployment from branch/tag: ${{ inputs.branch-or-tag }}' - ref: ${{ needs.artifact-check.outputs.artifact-build-commit }} + ref: ${{ needs.artifact-check.outputs.artifact_build_commit }} - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: - creds: ${{ secrets[needs.setup.outputs.azure-login-creds] }} + creds: ${{ secrets[needs.setup.outputs.azure_login_creds] }} - name: Retrieve Storage Account connection string for az sync - if: ${{ needs.setup.outputs.sync-utility == 'az-sync' }} + if: ${{ needs.setup.outputs.sync_utility == 'az-sync' }} id: retrieve-secrets-az-sync uses: bitwarden/gh-actions/get-keyvault-secrets@main with: - keyvault: ${{ needs.setup.outputs.retrieve-secrets-keyvault }} + keyvault: ${{ needs.setup.outputs.retrive_secrets_keyvault }} secrets: "sa-bitwarden-web-vault-dev-key-temp" - name: Retrieve Storage Account name and SPN credentials for azcopy - if: ${{ needs.setup.outputs.sync-utility == 'azcopy' }} + if: ${{ needs.setup.outputs.sync_utility == 'azcopy' }} id: retrieve-secrets-azcopy uses: bitwarden/gh-actions/get-keyvault-secrets@main with: - keyvault: ${{ needs.setup.outputs.retrieve-secrets-keyvault }} + keyvault: ${{ needs.setup.outputs.retrive_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 }}' @@ -363,7 +363,7 @@ jobs: run: unzip ${{ env._ENVIRONMENT_ARTIFACT }} - name: Sync to Azure Storage Account using az storage blob sync - if: ${{ needs.setup.outputs.sync-utility == 'az-sync' }} + if: ${{ needs.setup.outputs.sync_utility == 'az-sync' }} working-directory: apps/web run: | az storage blob sync \ @@ -373,7 +373,7 @@ jobs: --delete-destination=${{ inputs.force-delete-destination }} - name: Sync to Azure Storage Account using azcopy - if: ${{ needs.setup.outputs.sync-utility == 'azcopy' }} + if: ${{ needs.setup.outputs.sync_utility == 'azcopy' }} working-directory: apps/web env: AZCOPY_AUTO_LOGIN_TYPE: SPN @@ -397,7 +397,7 @@ jobs: uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' - environment-url: ${{ env._ENVIRONMENT_URL }} + environment_url: ${{ env._ENVIRONMENT_URL }} state: 'success' deployment-id: ${{ steps.deployment.outputs.deployment_id }} @@ -406,7 +406,7 @@ jobs: uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: '${{ secrets.GITHUB_TOKEN }}' - environment-url: ${{ env._ENVIRONMENT_URL }} + environment_url: ${{ env._ENVIRONMENT_URL }} state: 'failure' deployment-id: ${{ steps.deployment.outputs.deployment_id }} @@ -424,11 +424,11 @@ jobs: uses: bitwarden/gh-actions/report-deployment-status-to-slack@main with: project: Clients - environment: ${{ needs.setup.outputs.environment-name }} + environment: ${{ needs.setup.outputs.environment_name }} tag: ${{ inputs.branch-or-tag }} slack-channel: ${{ needs.notify-start.outputs.channel_id }} event: ${{ needs.azure-deploy.result }} url: https://github.com/bitwarden/clients/actions/runs/${{ github.run_id }} - commit-sha: ${{ needs.artifact-check.outputs.artifact-build-commit }} + commit-sha: ${{ needs.artifact-check.outputs.artifact_build_commit }} update-ts: ${{ needs.notify-start.outputs.ts }} AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a907618bd36..867de3844e7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -54,21 +54,25 @@ jobs: cache-dependency-path: '**/package-lock.json' node-version: ${{ steps.retrieve-node-version.outputs.node_version }} + - name: Install Node dependencies + run: npm ci + + - name: Lint unowned dependencies + run: npm run lint:dep-ownership + - name: Run linter - run: | - npm ci - npm run lint + run: npm run lint rust: name: Run Rust lint on ${{ matrix.os }} - runs-on: ${{ matrix.os || 'ubuntu-latest' }} + runs-on: ${{ matrix.os || 'ubuntu-24.04' }} strategy: matrix: os: - - ubuntu-latest - - macos-latest - - windows-latest + - ubuntu-24.04 + - macos-14 + - windows-2022 steps: - name: Checkout repo diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 0a561306797..ff85a30d3f6 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -43,8 +43,8 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version-output.outputs.version }} - deployment-id: ${{ steps.deployment.outputs.deployment_id }} + release_version: ${{ steps.version-output.outputs.version }} + deployment_id: ${{ steps.deployment.outputs.deployment_id }} defaults: run: working-directory: . @@ -88,7 +88,7 @@ jobs: needs: setup if: inputs.snap_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -125,7 +125,7 @@ jobs: needs: setup if: inputs.choco_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -165,7 +165,7 @@ jobs: needs: setup if: inputs.npm_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -222,7 +222,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -230,4 +230,4 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 5ef378ad439..69ccd841065 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -39,10 +39,10 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} - release-channel: ${{ steps.release-channel.outputs.channel }} - tag-name: ${{ steps.version.outputs.tag_name }} - deployment-id: ${{ steps.deployment.outputs.deployment_id }} + release_version: ${{ steps.version.outputs.version }} + release_channel: ${{ steps.release_channel.outputs.channel }} + tag_name: ${{ steps.version.outputs.tag_name }} + deployment_id: ${{ steps.deployment.outputs.deployment_id }} steps: - name: Branch check if: ${{ inputs.publish_type != 'Dry Run' }} @@ -76,7 +76,7 @@ jobs: fi - name: Get Version Channel - id: release-channel + id: release_channel run: | case "${{ steps.version.outputs.version }}" in *"alpha"*) @@ -100,7 +100,7 @@ jobs: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' environment: 'Desktop - Production' - description: 'Deployment ${{ steps.version.outputs.version }} to channel ${{ steps.release-channel.outputs.channel }} from branch ${{ github.ref_name }}' + description: 'Deployment ${{ steps.version.outputs.version }} to channel ${{ steps.release_channel.outputs.channel }} from branch ${{ github.ref_name }}' task: release electron-blob: @@ -108,8 +108,8 @@ jobs: runs-on: ubuntu-22.04 needs: setup env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -136,7 +136,7 @@ jobs: - name: Set staged rollout percentage env: - RELEASE_CHANNEL: ${{ needs.setup.outputs.release-channel }} + RELEASE_CHANNEL: ${{ needs.setup.outputs.release_channel }} ROLLOUT_PCT: ${{ inputs.rollout_percentage }} run: | echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml @@ -163,7 +163,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -171,7 +171,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} snap: name: Deploy Snap @@ -179,8 +179,8 @@ jobs: needs: setup if: inputs.snap_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -223,8 +223,8 @@ jobs: needs: setup if: inputs.choco_publish env: - _PKG_VERSION: ${{ needs.setup.outputs.release-version }} - _RELEASE_TAG: ${{ needs.setup.outputs.tag-name }} + _PKG_VERSION: ${{ needs.setup.outputs.release_version }} + _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -284,7 +284,7 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'success' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} - name: Update deployment status to Failure if: ${{ inputs.publish_type != 'Dry Run' && failure() }} @@ -292,4 +292,4 @@ jobs: with: token: '${{ secrets.GITHUB_TOKEN }}' state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment-id }} + deployment_id: ${{ needs.setup.outputs.deployment_id }} diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 75442187516..7e8722dc79f 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -23,7 +23,7 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} + release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -118,7 +118,7 @@ jobs: - name: Rename build artifacts env: - PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} run: | mv browser-source.zip browser-source-$PACKAGE_VERSION.zip mv dist-chrome.zip dist-chrome-$PACKAGE_VERSION.zip @@ -130,14 +130,14 @@ jobs: if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 with: - artifacts: 'browser-source-${{ needs.setup.outputs.release-version }}.zip, - dist-chrome-${{ needs.setup.outputs.release-version }}.zip, - dist-opera-${{ needs.setup.outputs.release-version }}.zip, - dist-firefox-${{ needs.setup.outputs.release-version }}.zip, - dist-edge-${{ needs.setup.outputs.release-version }}.zip' + artifacts: 'browser-source-${{ needs.setup.outputs.release_version }}.zip, + dist-chrome-${{ needs.setup.outputs.release_version }}.zip, + dist-opera-${{ needs.setup.outputs.release_version }}.zip, + dist-firefox-${{ needs.setup.outputs.release_version }}.zip, + dist-edge-${{ needs.setup.outputs.release_version }}.zip' commit: ${{ github.sha }} - tag: "browser-v${{ needs.setup.outputs.release-version }}" - name: "Browser v${{ needs.setup.outputs.release-version }}" + tag: "browser-v${{ needs.setup.outputs.release_version }}" + name: "Browser v${{ needs.setup.outputs.release_version }}" body: "" token: ${{ secrets.GITHUB_TOKEN }} draft: true diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 05c53f9752d..d16cd744d7d 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -23,7 +23,7 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} + release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: ${{ inputs.release_type }} project-type: ts @@ -75,7 +75,7 @@ jobs: if: ${{ inputs.release_type != 'Dry Run' }} uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 env: - PKG_VERSION: ${{ needs.setup.outputs.release-version }} + PKG_VERSION: ${{ needs.setup.outputs.release_version }} with: artifacts: "apps/cli/bw-oss-windows-${{ env.PKG_VERSION }}.zip, apps/cli/bw-oss-windows-sha256-${{ env.PKG_VERSION }}.txt, diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 3ec11c77852..08174dc552e 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -16,9 +16,9 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} - release-channel: ${{ steps.release-channel.outputs.channel }} - branch-name: ${{ steps.branch.outputs.branch-name }} + release_version: ${{ steps.version.outputs.version }} + release_channel: ${{ steps.release_channel.outputs.channel }} + branch_name: ${{ steps.branch.outputs.branch_name }} build_number: ${{ steps.increment-version.outputs.build_number }} node_version: ${{ steps.retrieve-node-version.outputs.node_version }} steps: @@ -47,7 +47,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: 'Initial Release' project-type: ts @@ -63,7 +63,7 @@ jobs: echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT - name: Get Version Channel - id: release-channel + id: release_channel run: | case "${{ steps.version.outputs.version }}" in *"alpha"*) @@ -102,7 +102,7 @@ jobs: git push -u origin $branch_name - echo "branch-name=$branch_name" >> $GITHUB_OUTPUT + echo "branch_name=$branch_name" >> $GITHUB_OUTPUT - name: Get Node Version id: retrieve-node-version @@ -116,7 +116,7 @@ jobs: runs-on: ubuntu-22.04 needs: setup env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -126,7 +126,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -195,8 +195,8 @@ jobs: - name: Upload auto-update artifact uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: ${{ needs.setup.outputs.release-channel }}-linux.yml - path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-linux.yml + name: ${{ needs.setup.outputs.release_channel }}-linux.yml + path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml if-no-files-found: error @@ -209,14 +209,14 @@ jobs: shell: pwsh working-directory: apps/desktop env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -385,8 +385,8 @@ jobs: - name: Upload auto-update artifact uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: ${{ needs.setup.outputs.release-channel }}.yml - path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release-channel }}.yml + name: ${{ needs.setup.outputs.release_channel }}.yml + path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml if-no-files-found: error @@ -395,7 +395,7 @@ jobs: runs-on: macos-13 needs: setup env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -405,7 +405,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -529,7 +529,7 @@ jobs: - setup - macos-build env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -539,7 +539,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -730,8 +730,8 @@ jobs: - name: Upload auto-update artifact uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: - name: ${{ needs.setup.outputs.release-channel }}-mac.yml - path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-mac.yml + name: ${{ needs.setup.outputs.release_channel }}-mac.yml + path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml if-no-files-found: error @@ -742,7 +742,7 @@ jobs: - setup - macos-build env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} + _PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} NODE_OPTIONS: --max_old_space_size=4096 defaults: @@ -752,7 +752,7 @@ jobs: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ needs.setup.outputs.branch-name }} + ref: ${{ needs.setup.outputs.branch_name }} - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -939,7 +939,7 @@ jobs: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' environment: 'Desktop - Beta' - description: 'Deployment ${{ needs.setup.outputs.release-version }} to channel ${{ needs.setup.outputs.release-channel }} from branch ${{ needs.setup.outputs.branch-name }}' + description: 'Deployment ${{ needs.setup.outputs.release_version }} to channel ${{ needs.setup.outputs.release_channel }} from branch ${{ needs.setup.outputs.branch_name }}' task: release - name: Login to Azure @@ -963,7 +963,7 @@ jobs: - name: Rename .pkg to .pkg.archive env: - PKG_VERSION: ${{ needs.setup.outputs.release-version }} + PKG_VERSION: ${{ needs.setup.outputs.release_version }} working-directory: apps/desktop/artifacts run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive @@ -1020,5 +1020,5 @@ jobs: git config --global url."https://".insteadOf ssh:// - name: Remove branch env: - BRANCH: ${{ needs.setup.outputs.branch-name }} + BRANCH: ${{ needs.setup.outputs.branch_name }} run: git push origin --delete $BRANCH diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index d9394347f60..ba934235b44 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -22,8 +22,8 @@ jobs: name: Setup runs-on: ubuntu-22.04 outputs: - release-version: ${{ steps.version.outputs.version }} - release-channel: ${{ steps.release-channel.outputs.channel }} + release_version: ${{ steps.version.outputs.version }} + release_channel: ${{ steps.release_channel.outputs.channel }} steps: - name: Checkout repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -40,7 +40,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@main + uses: bitwarden/gh-actions/release_version-check@main with: release-type: ${{ inputs.release_type }} project-type: ts @@ -49,7 +49,7 @@ jobs: monorepo-project: desktop - name: Get Version Channel - id: release-channel + id: release_channel run: | case "${{ steps.version.outputs.version }}" in *"alpha"*) @@ -97,10 +97,10 @@ jobs: - name: Create Release uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 - if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} + if: ${{ steps.release_channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} env: PKG_VERSION: ${{ steps.version.outputs.version }} - RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} + RELEASE_CHANNEL: ${{ steps.release_channel.outputs.channel }} with: artifacts: "apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-amd64.deb, apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm, diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index a914a2c4a7a..ac2733e765b 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -461,6 +461,7 @@ jobs: - name: Check out main branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: + fetch-depth: 0 ref: main token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index b0874b38cbf..a09e8137b65 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -66,10 +66,9 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@02ef91109b2d589e757aefcfb2854c2783fd7b19 # v4.0.0 + uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: args: > -Dsonar.organization=${{ github.repository_owner }} diff --git a/apps/browser/package.json b/apps/browser/package.json index 3adeb292b6d..9ad1805362e 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/browser", - "version": "2025.1.0", + "version": "2025.1.1", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 webpack", - "build:edge": "cross-env BROWSER=edge webpack", + "build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 webpack", "build:firefox": "cross-env BROWSER=firefox webpack", - "build:opera": "cross-env BROWSER=opera webpack", + "build:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 webpack", "build:safari": "cross-env BROWSER=safari webpack", "build:watch": "npm run build:watch:chrome", "build:watch:chrome": "npm run build:chrome -- --watch", @@ -30,6 +30,7 @@ "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari", "test": "jest", "test:watch": "jest --watch", - "test:watch:all": "jest --watchAll" + "test:watch:all": "jest --watchAll", + "test:clearCache": "jest --clear-cache" } } diff --git a/apps/browser/postcss.config.js b/apps/browser/postcss.config.js index c4513687e89..83e237f06e5 100644 --- a/apps/browser/postcss.config.js +++ b/apps/browser/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 46f119bdafe..8b4bbe23e04 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2324,6 +2324,9 @@ "message": "النطاقات", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "النطاقات المستبعدة" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden لن يطلب حفظ تفاصيل تسجيل الدخول لهذه النطافات لجميع الحسابات مسجلة الدخول. يجب عليك تحديث الصفحة لكي تصبح التغييرات نافذة المفعول." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "الموقع $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "تم حفظ تغييرات استبعاد النطاقات" }, @@ -2789,6 +2804,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "إنشاء اسم المستخدم" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 51bfe95a48a..86f1d07fb3f 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2324,6 +2324,9 @@ "message": "Domenlər", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Əngəllənmiş domenlər" + }, "excludedDomains": { "message": "İstisna edilən domenlər" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, giriş etmiş bütün hesablar üçün bu domenlərin giriş detallarını saxlamağı soruşmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." }, + "blockedDomainsDesc": { + "message": "Bu veb saytlar üçün avto-doldurma və digər əlaqəli özəlliklər təklif olunmayacaq. Dəyişikliklərin qüvvəyə minməsi üçün səhifəni təzələməlisiniz." + }, + "autofillBlockedNotice": { + "message": "Bu veb sayt üçün avto-doldurma əngəllənib. Bunu ayarlarda incələyin və ya dəyişdirin." + }, + "autofillBlockedTooltip": { + "message": "Bu veb saytda avto-doldurma əngəllənib. Ayarlarda incələyin." + }, "websiteItemLabel": { "message": "Veb sayt $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Əngəllənmiş domen dəyişiklikləri saxlanıldı" + }, "excludedDomainsSavedSuccess": { "message": "İstisna domen dəyişikliyi saxlanıldı" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "İstifadəçi adı yarat" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Bu elementə düzəliş etmə icazəniz yoxdur" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Bitwarden masaüstü tətbiqi bağlı olduğu üçün biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, "authenticating": { "message": "Kimlik doğrulama" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 46c804d27d9..036b1dfcb24 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2324,6 +2324,9 @@ "message": "Дамены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Выключаныя дамены" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Вэб-сайт $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генерыраваць імя карыстальніка" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 7cbbdc07be4..098e2b91051 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2324,6 +2324,9 @@ "message": "Домейни", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Блокирани домейни" + }, "excludedDomains": { "message": "Изключени домейни" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Битуорден няма да пита дали да запазва данните за вход в тези сайтове за всички регистрации, в които сте вписан(а). За да влезе правилото в сила, презаредете страницата." }, + "blockedDomainsDesc": { + "message": "Автоматичното попълване и други свързани функции няма да бъдат предлагани за тези уеб сайтове. Трябва да презаредите страницата, за да влязат в сила промените." + }, + "autofillBlockedNotice": { + "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате и промените това в настройките." + }, + "autofillBlockedTooltip": { + "message": "Автоматичното попълване е блокирано за този уеб сайт. Можете да прегледате това в настройките." + }, "websiteItemLabel": { "message": "Уеб сайт $number$ (адрес)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Промените на блокираните домейни са запазени" + }, "excludedDomainsSavedSuccess": { "message": "Промените на изключените домейни са запазени" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генериране на потр. име" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Нямате право за редактиране на този елемент" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Отключването с биометрични данни не е налично, тъй като приложението на Биуорден за компютър не работи." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, "authenticating": { "message": "Удостоверяване" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 2da7b40554c..f60fc2c9683 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 9f5a610c351..aaf04da98b7 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index b38783abc82..e7113d5aab1 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2324,6 +2324,9 @@ "message": "Dominis", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominis exclosos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no demanarà que es guarden les dades d'inici de sessió d'aquests dominis per a tots els comptes iniciats. Heu d'actualitzar la pàgina perquè els canvis tinguen efecte." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genera un nom d'usuari" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index bcc7020ffb3..eb2f9149daf 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2324,6 +2324,9 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, "excludedDomains": { "message": "Vyloučené domény" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude žádat o uložení přihlašovacích údajů pro tyto domény pro všechny přihlášené účty. Aby se změny projevily, musíte stránku obnovit." }, + "blockedDomainsDesc": { + "message": "Automatické vyplňování a další související funkce nebudou pro tyto webové stránky nabízeny. Aby se změny projevily, musíte stránku aktualizovat." + }, + "autofillBlockedNotice": { + "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to nebo to změňte v nastavení." + }, + "autofillBlockedTooltip": { + "message": "Automatické vyplňování je pro tento web zablokováno. Zkontrolujte to v nastavení." + }, "websiteItemLabel": { "message": "Webová stránka $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Změny v zablokovaných doménách byly uloženy" + }, "excludedDomainsSavedSuccess": { "message": "Vyloučené změny domény byly uloženy" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Vygenerovat uživatelské jméno" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nemáte oprávnění upravit tuto položku" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrické odemknutí není dostupné, protože je aplikace Bitwarden zavřena." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrické odemknutí není dostupné, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, "authenticating": { "message": "Ověřování" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 3bc85b10fb5..8ad494f0bfb 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Parthau wedi'u heithrio" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Gwall" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Cynhyrchu enw defnyddiwr" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 433cdebbb17..9008049e1a4 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2324,6 +2324,9 @@ "message": "Domæner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokerede domæner" + }, "excludedDomains": { "message": "Ekskluderede domæner" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden vil ikke anmode om at gemme login-detaljer for disse domæner for alle indloggede konti. Siden skal opfriskes for at effektuere ændringerne." }, + "blockedDomainsDesc": { + "message": "Autofyldning og andre relaterede funktioner tilbydes ikke på disse websteder. Siden skal opdateres for at effektuere ændringerne." + }, + "autofillBlockedNotice": { + "message": "Autoudfyldning er blokeret på dette websted. Gennemgå eller ændr dette i Indstillinger." + }, + "autofillBlockedTooltip": { + "message": "Autoudfyldning er blokeret på dette websted. Gennemgå i Indstillinger." + }, "websiteItemLabel": { "message": "Websted $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blokeret domæne-ændringer gemt" + }, "excludedDomainsSavedSuccess": { "message": "Ekskluderet domæne-ændringer gemt" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generér brugernavn" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Ingen tilladelse til at redigere dette emne" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisk oplåsning er utilgængelig, da Bitwarden-appen er lukket." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, "authenticating": { "message": "Godkender" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 2fc5297205a..fce84d1b431 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Gesperrte Domains" + }, "excludedDomains": { "message": "Ausgeschlossene Domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden wird für alle angemeldeten Konten nicht danach fragen Zugangsdaten für diese Domains speichern. Du musst die Seite neu laden, damit die Änderungen wirksam werden." }, + "blockedDomainsDesc": { + "message": "Automatisches Ausfüllen und andere zugehörige Funktionen werden für diese Webseiten nicht angeboten. Sie müssen die Seite aktualisieren, damit die Änderungen wirksam werden." + }, + "autofillBlockedNotice": { + "message": "Das automatische Ausfüllen ist für diese Website gesperrt. Dieses Verhalten kann in den Einstellungen überprüft oder geändert werden." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Änderungen gesperrter Domains gespeichert" + }, "excludedDomainsSavedSuccess": { "message": "Änderungen der ausgeschlossenen Domain gespeichert" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte den unten aufgelisteten Tresoreintrag bzw. die Tresoreinträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Benutzername generieren" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da die Bitwarden Desktop-App geschlossen ist." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." + }, "authenticating": { "message": "Authentifizierung" }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index a33bc4d0e20..bdb0eb2c17d 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1005,7 +1005,7 @@ "message": "Λίστα στοιχείων ταυτότητας στη σελίδα Καρτέλας για εύκολη αυτόματη συμπλήρωση." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Κάντε κλικ στα αντικείμενα για αυτόματη συμπλήρωση στην προβολή Θησαυ/κίου" }, "clearClipboard": { "message": "Εκκαθάριση Πρόχειρου", @@ -2324,6 +2324,9 @@ "message": "Τομείς", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Αποκλεισμένοι τομείς" + }, "excludedDomains": { "message": "Εξαιρούμενοι Τομείς" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Το Bitwarden δε θα ρωτήσει για να αποθηκεύσετε τα στοιχεία σύνδεσης για αυτούς τους τομείς, για όλους τους συνδεδεμένους λογαριασμούς. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." }, + "blockedDomainsDesc": { + "message": "Η αυτόματη συμπλήρωση και άλλες σχετικές λειτουργίες δεν θα προσφερθούν για αυτούς τους ιστότοπους. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Ιστοσελίδα $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Οι αλλαγές αποκλεισμένων τομέων αποθηκεύτηκαν" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Σφάλμα αποκρυπτογράφησης" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Το Bitwarden δεν μπόρεσε να αποκρυπτογραφήσει τα αντικείμενα θησαυ/κίου που αναφέρονται παρακάτω." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Δημιουργία ονόματος χρήστη" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Δεν έχετε δικαίωμα να επεξεργαστείτε αυτό το αντικείμενο" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή απαιτείται πρώτα το ξεκλείδωμα με PIN ή κωδικό πρόσβασης." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο προς το παρόν." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο λόγω εσφαλμένων ρυθμίσεων αρχείων συστήματος." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή η εφαρμογή Bitwarden επιφάνειας εργασίας είναι κλειστή." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Ταυτοποίηση" }, diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b72a909252b..51fb3a0a770 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2339,11 +2339,11 @@ "blockedDomainsDesc": { "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, - "autofillBlockedNotice": { - "message": "Autofill is blocked for this website. Review or change this in settings." + "autofillBlockedNoticeV2": { + "message": "Autofill is blocked for this website." }, - "autofillBlockedTooltip": { - "message": "Autofill is blocked on this website. Review in settings." + "autofillBlockedNoticeGuidance": { + "message": "Change this in settings" }, "websiteItemLabel": { "message": "Website $number$ (URI)", @@ -4007,8 +4007,8 @@ "passkeyRemoved": { "message": "Passkey removed" }, - "autofillSuggestions": { - "message": "Autofill suggestions" + "itemSuggestions": { + "message": "Suggested items" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 2c13f1b9259..7e938bb6b2c 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index b6031381d2d..70e725c3a88 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded Domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate Username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index d26716dd909..d9cd2517816 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2324,6 +2324,9 @@ "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominios excluidos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden no pedirá que se guarden los datos de acceso para estos dominios en todas las sesiones iniciadas. Debe actualizar la página para que los cambios surtan efecto." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generar nombre de usuario" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "No tiene permiso de editar este elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 069135ddb08..5245de4fb7e 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Väljajäetud domeenid" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genereeri kasutajanimi" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 47a56583c64..fc875be6da0 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Kanporatutako domeinuak" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Sortu erabiltzaile izena" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 2b8a5e20da1..d2362a2655f 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "دامنه های مستثنی" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "ایجاد نام کاربری" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 718c0631156..0dd96ef9a02 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2324,6 +2324,9 @@ "message": "Verkkotunnukset", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Ohitettavat verkkotunnukset" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ei pyydä kirjautumistietojen tallennusta näillä verkkotunnuksilla. Koskee kaikkia kirjautuneita tilejä. Ota muutokset käyttöön päivittämällä sivu." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Verkkotunnus $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Rajoitettujen verkkotunnusten muutokset tallennettiin" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Luo käyttäjätunnus" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Sinulla ei ole oikeutta muokata tätä kohdetta" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Todennetaan" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index a504cbd3174..d0fdf1018fb 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Inilayo na Domain" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Lumikha ng username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 9566f457861..913391d218c 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -2324,6 +2324,9 @@ "message": "Domaines", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domaines bloqués" + }, "excludedDomains": { "message": "Domaines exclus" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden ne demandera pas d'enregistrer les détails de connexion pour ces domaines pour tous les comptes connectés. Vous devez actualiser la page pour que les modifications prennent effet." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Site web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Changements de domaines exclus enregistrés" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Générer un nom d'utilisateur" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Vous n'êtes pas autorisé à modifier cet élément" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authentification" }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index b3eaae64486..e655159f246 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2324,6 +2324,9 @@ "message": "Dominios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Dominios excluídos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden non ofrecerá gardar contas para estes dominios en ningunha das sesións iniciadas. Recarga a páxina para que os cambios fornezan efecto." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Dominios excluídos gardados" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Xerar nome de usuario" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Non tes permiso para modificar esta entrada" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autenticando" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 5101c11a653..1a3057ac291 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "שגיאה" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 10ad502be82..fd4a6612af4 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "बहिष्कृत डोमेन" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "एरर" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "उपयोगकर्ता नाम बनाएँ" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index c93922cb913..331bc109309 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2324,6 +2324,9 @@ "message": "Domene", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Izuzete domene" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden neće nuditi spremanje podataka za prijavu za ove domene za sve prijavljene račune. Moraš osvježiti stranicu kako bi promjene stupile na snagu." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web stranica $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Spremljene promjene izuzete domene" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Pogreška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generiraj korisničko ime" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nemaš prava za uređivanje ove stavke" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autentifikacija" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 8b8c9722909..0aea2c7eced 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2324,6 +2324,9 @@ "message": "Tartomány", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Letiltott tartományok" + }, "excludedDomains": { "message": "Kizárt domainek" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "A Bitwarden nem kéri a bejelentkezési adatok mentését ezeknél a tartományoknál az összes bejelentkezési fiókra vonatkozva. A változtatások életbe lépéséhez frissíteni kell az oldalt." }, + "blockedDomainsDesc": { + "message": "Az automatikus kitöltés és az egyéb kapcsolódó funkciók ezeken a webhelyeken nincsenek a kínálatban. A változtatások életbe lépéséhez frissíteni kell az oldalt." + }, + "autofillBlockedNotice": { + "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át vagy módosítsuk ezt a beállításokban." + }, + "autofillBlockedTooltip": { + "message": "Az automatikus kitöltés le van tiltva ezen a webhelyen. Tekintsük át ezt a beállításokban." + }, "websiteItemLabel": { "message": "Webhely $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "A letiltott tartomány módosítások mentésre kerültek." + }, "excludedDomainsSavedSuccess": { "message": "A kizárt tartomány módosítások mentésre kerültek." }, @@ -2789,6 +2804,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Felhasználónév generálása" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nincs jogosulltság ezen elem szerkesztéséheu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "A biometrikus feloldás nem érhető el, mert a Bitwarden asztali alkalmazás be van zárva." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 7b1ad51e0b7..82776a8e82b 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2324,6 +2324,9 @@ "message": "Domain", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domain yang Dikecualikan" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden tidak akan meminta untuk menyimpan rincian login untuk domain tersebut. Anda harus menyegarkan halaman agar perubahan diterapkan." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Situs web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Perubahan domain yang diabaikan telah disimpan" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Buat nama pengguna baru" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 6490a441832..04e2c4ee64f 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2324,6 +2324,9 @@ "message": "Domini", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domini bloccati" + }, "excludedDomains": { "message": "Domini esclusi" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden non chiederà di salvare le credenziali di accesso per questi domini per tutti gli account sul dispositivo. Ricarica la pagina affinché le modifiche abbiano effetto." }, + "blockedDomainsDesc": { + "message": "Per questi siti, l'auto-completamento e funzionalità simili non saranno disponibili. Ricarica la pagina per applicare le modifiche." + }, + "autofillBlockedNotice": { + "message": "L'auto-completamento è bloccato per questo sito. Modifica questa scelta nelle impostazioni." + }, + "autofillBlockedTooltip": { + "message": "L'auto-completamento è bloccato per questo sito. Verifica nelle impostazioni." + }, "websiteItemLabel": { "message": "Sito $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Modifiche ai domini bloccati salvate" + }, "excludedDomainsSavedSuccess": { "message": "Modifiche del dominio escluso salvate" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Genera nome utente" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Non hai i permessi per modificare questo elemento" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Lo sblocco biometrico non è disponibile perché l'app desktop Bitwarden è chiusa." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, "authenticating": { "message": "Autenticazione" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 42dd73020ec..cc1f34e4985 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2324,6 +2324,9 @@ "message": "ドメイン", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "除外するドメイン" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden はログインしているすべてのアカウントで、これらのドメインのログイン情報を保存するよう要求しません。 変更を有効にするにはページを更新する必要があります。" }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "ウェブサイト $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "除外ドメインの変更を保存しました" }, @@ -2789,6 +2804,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "ユーザー名を生成" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "このアイテムを編集する権限がありません" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "認証中" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 5da73c6755b..50fdc6613c5 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2324,6 +2324,9 @@ "message": "დომენები", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "შეცდომა" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "ავთენტიკაცია" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 09e26c18b5b..3f9e99e5637 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "ಹೊರತುಪಡಿಸಿದ ಡೊಮೇನ್ಗಳು" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index d6da55f600d..4ac6d281b09 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2324,6 +2324,9 @@ "message": "도메인", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "제외된 도메인" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "BItwarden은 로그인한 모든 계정에 대해 이러한 도메인에 대한 로그인 세부 정보를 저장하도록 요청하지 않습니다. 변경 사항을 적용하려면 페이지를 새로 고쳐야 합니다" }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "웹사이트 $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "제외된 도메인 변경 사항 저장됨" }, @@ -2789,6 +2804,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "아이디 생성" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "아이템을 수정할 권한이 없습니다." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "인증 중" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index eaf1cb9f9db..3c81df00f10 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2324,6 +2324,9 @@ "message": "Domenai", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Išskirti domenai" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "„Bitwarden“ neprašys išsaugoti prisijungimo detalių šiems domenams, visose prisijungusiose paskyrose. Turite atnaujinti puslapį, kad pokyčiai pradėtų galioti." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Klaida" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generuoti vartotojo vardą" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 2c2a9c3c69c..fc682ced389 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2324,6 +2324,9 @@ "message": "Domēna vārdi", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Liegtie domēna vārdi" + }, "excludedDomains": { "message": "Izņēmuma domēni" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nevaicās saglabāt pieteikšanās datus visiem šī domēna kontiem, kuri ir pieteikušies. Ir jāpārlādē lapa, lai iedarbotos izmaiņas." }, + "blockedDomainsDesc": { + "message": "Automātiskā aizpilde un citas saistītās iespējas šajās tīmekļvietnēs netiks piedāvātas. Ir jāatsvaidzina lapa, lai izmaiņas iedarbotos." + }, + "autofillBlockedNotice": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo pārskatīt vai mainīt var iestatījumos." + }, + "autofillBlockedTooltip": { + "message": "Automātiskā aizpilde šajā tīmekļvietnē ir liegta. Šo var pārskatīt iestatījumos." + }, "websiteItemLabel": { "message": "Tīmekļvietne $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Liegtā domēna vārda izmaiņas sglabātas" + }, "excludedDomainsSavedSuccess": { "message": "Saglabātas vērā neņemto domēna vārdu izmaiņas" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Izveidot lietotājvārdu" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nav nepieciešamo atļauju, lai labotu šo vienumu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Atslēgšana ar biometriju nav pieejama, jo Bitwarden darbvirsmas lietotne ir aizvērta." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, "authenticating": { "message": "Autentificē" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index cd210c85ce1..4cbbfc46d6a 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index d156e6d6458..cbb0b1bdf1a 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 16469051a0c..3a12c9ae4f4 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -20,19 +20,19 @@ "message": "Opprett en konto" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Er du ny til Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logg inn med passnøkkel" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Velkommen tilbake" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Velg et sterkt passord" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -138,31 +138,31 @@ "message": "Kopier sikkerhetskoden" }, "copyName": { - "message": "Copy name" + "message": "Kopiér navn" }, "copyCompany": { "message": "Copy company" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopiér fødselsnummer" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopiér passnummer" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopiér lisensnummer" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Kopiér privat nøkkel" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Kopiér offentlig nøkkel" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Kopiér fingeravtrykk" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopiér $FIELD$", "placeholders": { "field": { "content": "$1", @@ -171,13 +171,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopiér nettsted" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopiér notater" }, "fill": { - "message": "Fill", + "message": "Fyll", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +193,10 @@ "message": "Auto-utfyll identitet" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Fyll inn verifiseringskode" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Fyll inn verifiseringskode", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -239,13 +239,13 @@ "message": "Legg til en gjenstand" }, "accountEmail": { - "message": "Account email" + "message": "Kontoens E-postadresse" }, "requestHint": { - "message": "Request hint" + "message": "Be om et hint" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Be om passordhint" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Enter your account email address and your password hint will be sent to you" @@ -316,7 +316,7 @@ "message": "Logg ut" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Om Bitwarden" }, "about": { "message": "Om" @@ -325,7 +325,7 @@ "message": "More from Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Vil du fortsette til bitwarden.com?" }, "bitwardenForBusiness": { "message": "Bitwarden for Business" @@ -382,7 +382,7 @@ "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Ingen mapper er lagt til" }, "createFoldersToOrganize": { "message": "Create folders to organize your vault items" @@ -475,7 +475,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Inkluder store bokstaver", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Inkluder små bokstaver", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -499,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Inkluder spesialtegn", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -526,7 +526,7 @@ "message": "Minste antall spesialtegn" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Unngå forvekslingsbare tegn", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -564,19 +564,19 @@ "message": "Favoritt" }, "unfavorite": { - "message": "Unfavorite" + "message": "Fjern favorittstempel" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Gjenstand lagt til i favorittene" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Gjenstand fjernet fra favorittene" }, "notes": { "message": "Notater" }, "privateNote": { - "message": "Private note" + "message": "Privat notat" }, "note": { "message": "Notat" @@ -597,10 +597,10 @@ "message": "Åpne" }, "launchWebsite": { - "message": "Launch website" + "message": "Åpne nettstedet" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Åpne nettstedet $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -657,7 +657,7 @@ "message": "Your account is locked" }, "or": { - "message": "or" + "message": "eller" }, "unlock": { "message": "Lås opp" @@ -745,7 +745,7 @@ "message": "Your master password cannot be recovered if you forget it!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Få et hint om hovedpassordet" }, "errorOccurred": { "message": "En feil har oppstått" @@ -785,10 +785,10 @@ "message": "You have been logged in!" }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Du har vellykket logget inn" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Du kan lukke dette vinduet" }, "masterPassSent": { "message": "Vi har sendt deg en E-post med hintet til superpassordet." @@ -834,10 +834,10 @@ "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Lær mer om autentisering" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Kopier autentiseringsnøkkel (TOTP)" }, "loggedOut": { "message": "Logget av" @@ -852,19 +852,19 @@ "message": "Logg inn" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logg inn på Bitwarden" }, "restartRegistration": { "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Utløpt lenke" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du har kanskje allerede en konto" }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" @@ -1049,7 +1049,7 @@ "message": "Lås opp" }, "additionalOptions": { - "message": "Additional options" + "message": "Ekstra innstillinger" }, "enableContextMenuItem": { "message": "Vis alternativer for kontekstmeny" @@ -1113,7 +1113,7 @@ "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "exportTypeHeading": { - "message": "Export type" + "message": "Eksporttype" }, "accountRestricted": { "message": "Account restricted" @@ -1126,7 +1126,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "Advarsel", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1206,7 +1206,7 @@ "message": "Fil" }, "fileToShare": { - "message": "File to share" + "message": "Filen som skal deles" }, "selectFile": { "message": "Velg en fil." @@ -1242,7 +1242,7 @@ "message": "1 GB med kryptert fillagring for filvedlegg." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "Nødtilgang." }, "premiumSignUpTwoStepOptions": { "message": "Proprietary two-step login options such as YubiKey and Duo." @@ -1413,7 +1413,7 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Skriv inn koden du har fått tilsendt på E-post." }, "selfHostedEnvironment": { "message": "Selvbetjent miljø" @@ -1466,7 +1466,7 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" @@ -1514,7 +1514,7 @@ "message": "Kompromitterte eller upålitelige nettsider kan utnytte auto-utfylling når du laster inn siden." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Lær mer om risikoer" }, "learnMoreAboutAutofill": { "message": "Lær mer om auto-utfylling" @@ -1768,10 +1768,10 @@ "message": "Identitet" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH-nøkkel" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Ny $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1780,7 +1780,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Rediger $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1789,7 +1789,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Vis $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1801,7 +1801,7 @@ "message": "Passordhistorikk" }, "generatorHistory": { - "message": "Generator history" + "message": "Generatorhistorikk" }, "clearGeneratorHistoryTitle": { "message": "Clear generator history" @@ -1846,7 +1846,7 @@ "message": "Sikre notiser" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH-nøkler" }, "clear": { "message": "Tøm", @@ -1929,7 +1929,7 @@ "message": "Tøm historikk" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Ingenting å vise" }, "nothingGeneratedRecently": { "message": "You haven't generated anything recently" @@ -2017,7 +2017,7 @@ "message": "Lås opp med biometri" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Lås opp med hovedpassord" }, "awaitDesktop": { "message": "Venter på bekreftelse fra skrivebordsprogrammet" @@ -2029,7 +2029,7 @@ "message": "Lås med hovedpassordet når du starter nettleseren på nytt" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Krev hovedpassord ved omstart av nettleseren" }, "selectOneCollection": { "message": "Du må velge minst én samling." @@ -2041,22 +2041,22 @@ "message": "Klon" }, "passwordGenerator": { - "message": "Password generator" + "message": "Passordgenerator" }, "usernameGenerator": { - "message": "Username generator" + "message": "Brukernavngenerator" }, "useThisPassword": { "message": "Bruk dette passordet" }, "useThisUsername": { - "message": "Use this username" + "message": "Bruk dette brukernavnet" }, "securePasswordGenerated": { "message": "Secure password generated! Don't forget to also update your password on the website." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Bruk denne generatoren", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { @@ -2096,7 +2096,7 @@ "message": "Gjenopprettet objekt" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Har du allerede en konto?" }, "vaultTimeoutLogOutConfirmation": { "message": "Hvis du logger ut, fjerner du all tilgang til hvelvet ditt og krever online godkjenning etter tidsavbrudd. Er du sikker på at du vil bruke denne innstillingen?" @@ -2108,7 +2108,7 @@ "message": "Autofyll og lagre" }, "fillAndSave": { - "message": "Fill and save" + "message": "Fyll og lagre" }, "autoFillSuccessAndSavedUri": { "message": "Autoutfylt objekt og lagret URI" @@ -2195,10 +2195,10 @@ "message": "Avslutt abonnement" }, "atAnyTime": { - "message": "at any time." + "message": "når som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Ved å fortsette, samtykker du til" }, "and": { "message": "og" @@ -2324,6 +2324,9 @@ "message": "Domener", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokkerte domener" + }, "excludedDomains": { "message": "Ekskluderte domener" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,11 +2363,14 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, "limitSendViews": { - "message": "Limit views" + "message": "Begrens visninger" }, "limitSendViewsHint": { "message": "No one can view this Send after the limit is reached.", @@ -2383,7 +2398,7 @@ "message": "Tekst" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "Teksten som skal deles" }, "sendTypeFile": { "message": "Fil" @@ -2619,7 +2634,7 @@ "message": "Velg mappe …" }, "noFoldersFound": { - "message": "No folders found", + "message": "Ingen mapper ble funnet", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { @@ -2631,7 +2646,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "av $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2789,6 +2804,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Dekrypteringsfeil" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generer brukernavn" }, @@ -2796,7 +2825,7 @@ "message": "Generate email" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2861,7 +2890,7 @@ "message": "Generer et e-postalias med en ekstern videresendingstjeneste." }, "forwarderDomainName": { - "message": "Email domain", + "message": "E-postdomene", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { @@ -2883,11 +2912,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Nettsted: $WEBSITE$. Generert av Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2931,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ugyldig $SERVICENAME$-domene.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -3068,16 +3097,16 @@ "message": "Send varslingen på nytt" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "Vis alle påloggingsalternativer" }, "notificationSentDevice": { "message": "Et varsel er sendt til enheten din." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Et varsel ble sendt til enheten din" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" @@ -3086,7 +3115,7 @@ "message": "You will be notified once the request is approved" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Trenger du et annet alternativ?" }, "loginInitiated": { "message": "Login initiated" @@ -3188,7 +3217,7 @@ "message": "Device approval required. Select an approval option below:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Enhetsgodkjennelse kreves" }, "selectAnApprovalOptionBelow": { "message": "Select an approval option below" @@ -3212,7 +3241,7 @@ "message": "Organization SSO identifier is required." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Oppretter en konto på" }, "checkYourEmail": { "message": "Check your email" @@ -3230,7 +3259,7 @@ "message": "Gå tilbake" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "for å redigere E-postadressen din." }, "eu": { "message": "EU", @@ -3258,7 +3287,7 @@ "message": "Du vil bli varslet når det er godkjent." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Har du problemer med å logge inn?" }, "loginApproved": { "message": "Innlogging godkjent" @@ -3270,7 +3299,7 @@ "message": "Active user email not found. Logging you out." }, "deviceTrusted": { - "message": "Device trusted" + "message": "Enheten er betrodd" }, "sendsNoItemsTitle": { "message": "No active Sends", @@ -3421,7 +3450,7 @@ "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "Feil under importering. Sjekk loggkonsollen for detaljer.", "description": "Notification message for when an import has failed." }, "importNetworkError": { @@ -3458,7 +3487,7 @@ "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "Bitwardens autoutfyllingsmeny", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { @@ -3514,7 +3543,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nytt kort", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -3544,7 +3573,7 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "Importeringsfeil" }, "importErrorDesc": { "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." @@ -3708,16 +3737,16 @@ "message": "Vault data exported" }, "typePasskey": { - "message": "Passkey" + "message": "Passnøkkel" }, "accessing": { "message": "Accessing" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Innlogget!" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Passkoden vil ikke bli kopiert" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -3747,10 +3776,10 @@ "message": "Bekreft" }, "savePasskey": { - "message": "Save passkey" + "message": "Lagre passnøkkel" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Lagre passnøkkelen som en ny pålogging" }, "chooseCipherForPasskeySave": { "message": "Choose a login to save this passkey to" @@ -3813,7 +3842,7 @@ "message": "Approve the login request in your authentication app or enter a one-time passcode." }, "passcode": { - "message": "Passcode" + "message": "Passkode" }, "lastPassMasterPassword": { "message": "LastPass-hovedpassord" @@ -3853,7 +3882,7 @@ "message": "Bytt kontoer" }, "switchToAccount": { - "message": "Switch to account" + "message": "Bytt til konto" }, "activeAccount": { "message": "Aktiv konto" @@ -3874,13 +3903,13 @@ "message": "låst opp" }, "server": { - "message": "server" + "message": "tjener" }, "hostedAt": { "message": "betjent hos" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Bruk enhets- eller maskinvarenøkkel" }, "justOnce": { "message": "Kun én gang" @@ -3902,7 +3931,7 @@ "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Vil du fortsette til nettleserinnstillingene?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { @@ -3946,7 +3975,7 @@ "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "Gjør det til standarden", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { @@ -3954,7 +3983,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Passordet ble lagret!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { @@ -3962,7 +3991,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Passordet ble oppdatert!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -3973,19 +4002,19 @@ "message": "Suksess" }, "removePasskey": { - "message": "Remove passkey" + "message": "Fjern passordnøkkel" }, "passkeyRemoved": { "message": "Passkey removed" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Autoutfyllingsforslag" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to autofill" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Hvelvet ditt er tomt" }, "noItemsMatchSearch": { "message": "No items match your search" @@ -4014,7 +4043,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "Flere innstillinger, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4024,7 +4053,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "Flere innstillinger - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4034,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "Vis gjenstand - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4044,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "Autoutfyll - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4057,7 +4086,7 @@ "message": "No values to copy" }, "assignToCollections": { - "message": "Assign to collections" + "message": "Legg til i samlinger" }, "copyEmail": { "message": "Copy email" @@ -4087,7 +4116,7 @@ "message": "Error assigning target folder." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Vis gjenstander i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4097,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tilbake til $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4110,7 +4139,7 @@ "message": "Ny" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Fjern $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4120,13 +4149,13 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "Gjenstander uten mappe" }, "itemDetails": { - "message": "Item details" + "message": "Gjenstandens detaljer" }, "itemName": { - "message": "Item name" + "message": "Gjenstandens navn" }, "cannotRemoveViewOnlyCollections": { "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", @@ -4154,10 +4183,10 @@ "message": "Tilleggsinformasjon" }, "itemHistory": { - "message": "Item history" + "message": "Gjenstandshistorikk" }, "lastEdited": { - "message": "Last edited" + "message": "Nyligst redigert" }, "ownerYou": { "message": "Owner: You" @@ -4172,10 +4201,10 @@ "message": "Last opp" }, "addAttachment": { - "message": "Add attachment" + "message": "Legg til vedlegg" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Maksimal filstørrelse er 500 MB" }, "deleteAttachmentName": { "message": "Delete attachment $NAME$", @@ -4187,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "Last ned $NAME$", "placeholders": { "name": { "content": "$1", @@ -4211,10 +4240,10 @@ "message": "Filter vault" }, "filterApplied": { - "message": "One filter applied" + "message": "Ett filter er benyttet" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "$COUNT$ filtre er benyttet", "placeholders": { "count": { "content": "$1", @@ -4223,16 +4252,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "Personlige detaljer" }, "identification": { - "message": "Identification" + "message": "Identifikasjon" }, "contactInfo": { "message": "Kontaktinfo" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Last ned - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4241,17 +4270,17 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "kortnummeret slutter med", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { "message": "Legitimasjoner for innlogging" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Autentiseringsnøkkel" }, "autofillOptions": { - "message": "Autofill options" + "message": "Autoutfyllings-innstillinger" }, "websiteUri": { "message": "Website (URI)" @@ -4267,7 +4296,7 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Nettsted lagt til" }, "addWebsite": { "message": "Legg til nettsted" @@ -4276,7 +4305,7 @@ "message": "Slett nettsted" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "Standard ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4307,16 +4336,16 @@ "message": "Autofill on page load?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Utløpt kort" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" }, "cardDetails": { - "message": "Card details" + "message": "Kortdetaljer" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$-detaljer", "placeholders": { "brand": { "content": "$1", @@ -4328,7 +4357,7 @@ "message": "Aktiver animasjoner" }, "showAnimations": { - "message": "Show animations" + "message": "Vis animasjoner" }, "addAccount": { "message": "Legg til konto" @@ -4340,15 +4369,15 @@ "message": "Data" }, "passkeys": { - "message": "Passkeys", + "message": "Passnøkler", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "Passord", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "Logg inn med passnøkkel", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { @@ -4373,16 +4402,16 @@ } }, "addField": { - "message": "Add field" + "message": "Legg til felt" }, "add": { "message": "Legg til" }, "fieldType": { - "message": "Field type" + "message": "Felttype" }, "fieldLabel": { - "message": "Field label" + "message": "Feltetikett" }, "textHelpText": { "message": "Use text fields for data like security questions" @@ -4412,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "Slett $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ er lagt til", "placeholders": { "label": { "content": "$1", @@ -4543,7 +4572,7 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "Gjenstandens plassering" }, "fileSend": { "message": "File Send" @@ -4558,7 +4587,7 @@ "message": "Text Sends" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden har fått et nytt utseende!" }, "bitwardenNewLookDesc": { "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" @@ -4579,16 +4608,16 @@ "message": "Enterprise policy requirements have been applied to this setting" }, "sshPrivateKey": { - "message": "Private key" + "message": "Privat nøkkel" }, "sshPublicKey": { - "message": "Public key" + "message": "Offentlig nøkkel" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Fingeravtrykk" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Nøkkeltype" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4609,22 +4638,22 @@ "message": "Minimum custom timeout is 1 minute." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "Ytterligere innhold er tilgjengelig" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, "showCharacterCount": { - "message": "Show character count" + "message": "Vis tegntelleren" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "Skjul tegntelleren" }, "itemsInTrash": { - "message": "Items in trash" + "message": "Gjenstander i papirkurven" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Ingen gjenstander i papirkurven" }, "noItemsInTrashDesc": { "message": "Items you delete will appear here and be permanently deleted after 30 days" @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autentiserer" }, @@ -4653,11 +4709,11 @@ "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "Vil du lagre påloggingen i Bitwarden?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "Mellomrom", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { @@ -4665,15 +4721,15 @@ "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "Baklengs apostrof", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "Utropstegn", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "Alfakrøll", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { @@ -4681,11 +4737,11 @@ "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "Dollartegn", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "Prosenttegn", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { @@ -4693,7 +4749,7 @@ "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "Prosenttegn", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { @@ -4701,23 +4757,23 @@ "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "Venstre parantes", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "Høyre parantes", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "Understrek", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "Bindestrek", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "Plusstegn", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { @@ -4725,19 +4781,19 @@ "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "Venstre krøllparentes", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "Høyre krøllparentes", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "Venstre firkantparantes", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "Høyre firkantparantes", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { @@ -4745,69 +4801,69 @@ "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "Skråstrek bakover", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "Kolon", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "Semikolon", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "Hermetegn", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "Apostrofe", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "Mindre enn", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "Større enn", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "Komma", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "Tidsperiode", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "Spørsmålstegn", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "Skråstrek", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "Små bokstaver" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Store bokstaver" }, "generatedPassword": { - "message": "Generated password" + "message": "Generert passord" }, "compactMode": { - "message": "Compact mode" + "message": "Kompakt modus" }, "beta": { "message": "Beta" }, "importantNotice": { - "message": "Important notice" + "message": "Viktig melding" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "Sett opp 2-trinnspålogging" }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." @@ -4828,7 +4884,7 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Nei, det gjør jeg ikke" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Yes, I can reliably access my email" @@ -4837,15 +4893,15 @@ "message": "Turn on two-step login" }, "changeAcctEmail": { - "message": "Change account email" + "message": "Endre kontoens E-postadresse" }, "extensionWidth": { - "message": "Extension width" + "message": "Utvidelsens bredde" }, "wide": { - "message": "Wide" + "message": "Bred" }, "extraWide": { - "message": "Extra wide" + "message": "Ekstra bred" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index d167ba220c1..0112ded1983 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2324,6 +2324,9 @@ "message": "Domeinen", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Geblokkeerde domeinen" + }, "excludedDomains": { "message": "Uitgesloten domeinen" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden zal voor deze domeinen niet vragen om de wachtwoorden op te slaan voor alle ingelogde accounts. Je moet de pagina verversen om de wijzigingen op te slaan." }, + "blockedDomainsDesc": { + "message": "Autofill en andere gerelateerde functies worden niet aangeboden voor deze websites. Vernieuw de pagina om de wijzigingen toe te passen." + }, + "autofillBlockedNotice": { + "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk of verander dit in de instellingen." + }, + "autofillBlockedTooltip": { + "message": "Automatisch invullen is geblokkeerd voor deze website. Bekijk in de instellingen." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Wijzigingen in geblokkeerde domeinen opgeslagen" + }, "excludedDomainsSavedSuccess": { "message": "Uitgesloten domeinwijzigingen opgeslagen" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gebruikersnamen genereren" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Je hebt geen toestemming om dit item te bewerken" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat de Bitwarden-desktopapp is afgesloten." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, "authenticating": { "message": "Aan het inloggen" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index a429059ea7d..4b7d3a19fc4 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2324,6 +2324,9 @@ "message": "Domeny", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Wykluczone domeny" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Aplikacja Bitwarden nie będzie proponować zapisywania danych logowania dla tych domen dla wszystkich zalogowanych kont. Musisz odświeżyć stronę, aby zastosowywać zmiany." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Strona internetowa $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Zmiany w wykluczonych domenach zapisane" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Wygeneruj nazwę użytkownika" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Nie masz uprawnień do edycji tego elementu" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Uwierzytelnianie" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index cd0c9979103..e3409b1da52 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2324,6 +2324,9 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domínios Excluídos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "O Bitwarden não irá pedir para salvar os detalhes de credencial para estes domínios. Você deve atualizar a página para que as alterações entrem em vigor." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Mudanças de domínios excluídos salvas" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gerar Usuário" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Você não tem permissão para editar este arquivo" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Autenticando" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index def50289ae6..6b3c190f0b5 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2324,6 +2324,9 @@ "message": "Domínios", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Domínios bloqueados" + }, "excludedDomains": { "message": "Domínios excluídos" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "O Bitwarden não pedirá para guardar os detalhes de início de sessão destes domínios para todas as contas com sessão iniciada. É necessário atualizar a página para que as alterações tenham efeito." }, + "blockedDomainsDesc": { + "message": "O preenchimento automático e outras funcionalidades relacionadas não serão disponibilizados para estes sites. É necessário atualizar a página para que as alterações tenham efeito." + }, + "autofillBlockedNotice": { + "message": "O preenchimento automático está bloqueado para este site. Reveja ou altere esta opção nas definições." + }, + "autofillBlockedTooltip": { + "message": "O preenchimento automático está bloqueado neste site. Reveja nas definições." + }, "websiteItemLabel": { "message": "Site $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Alterações do domínio bloqueado guardadas" + }, "excludedDomainsSavedSuccess": { "message": "Alterações do domínio excluído guardadas" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Gerar nome de utilizador" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Não tem permissão para editar este item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueio biométrico está atualmente indisponível." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "O desbloqueio biométrico não está disponível porque a app para computador Bitwarden está fechada." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." + }, "authenticating": { "message": "A autenticar" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 58c0a313f69..114c01aff44 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Domenii excluse" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generare nume de utilizator" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index acdee563ceb..53f31813ac5 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2324,6 +2324,9 @@ "message": "Домены", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблокированные домены" + }, "excludedDomains": { "message": "Исключенные домены" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не будет предлагать сохранение логинов для этих доменов для всех авторизованных аккаунтов. Для вступления изменений в силу необходимо обновить страницу." }, + "blockedDomainsDesc": { + "message": "Автозаполнение и другие связанные с ним функции не будут предлагаться для этих сайтов. Чтобы изменения вступили в силу, необходимо обновить страницу." + }, + "autofillBlockedNotice": { + "message": "Автозаполнение для этого сайта заблокировано. Просмотрите или измените это в настройках." + }, + "autofillBlockedTooltip": { + "message": "Автозаполнение на этом сайте заблокировано. Просмотрите в настройках." + }, "websiteItemLabel": { "message": "Сайт $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Изменения в заблокированном домене сохранены" + }, "excludedDomainsSavedSuccess": { "message": "Изменения в исключенном домене сохранены" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Создать имя пользователя" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "У вас нет разрешения на редактирование этого элемента" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Биометрическая разблокировка недоступна, поскольку Bitwarden для компьютера закрыт." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, "authenticating": { "message": "Аутентификация" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index b25c2fd30d5..56cf378344f 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "බැහැර වසම්" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 5d11227f003..08bfcc79f6a 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2324,6 +2324,9 @@ "message": "Domény", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blokované domény" + }, "excludedDomains": { "message": "Vylúčené domény" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden nebude požadovať ukladanie prihlasovacích údajov pre tieto domény pre všetky prihlásené účty. Aby sa zmeny prejavili, musíte stránku obnoviť." }, + "blockedDomainsDesc": { + "message": "Automatické vypĺňanie a ďalšie súvisiace funkcie sa na týchto webových stránkach nebudú ponúkať. Aby sa zmeny prejavili, musíte stránku obnoviť." + }, + "autofillBlockedNotice": { + "message": "Automatické vypĺňanie je pre túto webovú stránku zablokované. Skontrolujte alebo zmeňte to v nastaveniach." + }, + "autofillBlockedTooltip": { + "message": "Automatické vypĺňanie je na tejto webovej stránke zablokované. Pozrite v nastaveniach." + }, "websiteItemLabel": { "message": "Webstránka $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Zmeny v blokovaných doménach boli uložené" + }, "excludedDomainsSavedSuccess": { "message": "Uložené zmeny vylúčenej domény" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Vygenerovať používateľské meno" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Na úpravu tejto položky nemáte oprávnenie" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože aplikácia Bitwarden pre desktop je zatvorená." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Odomykanie biometrickými údajmi je nedostupné, pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je momentálne z neznámych dôvodov nedostupné." + }, "authenticating": { "message": "Overuje sa" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 490f991d252..2d2ee455415 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Izključene domene" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Napaka" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Ustvari uporabniško ime" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index d515c2a0c6b..01d95a6ed1b 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2324,6 +2324,9 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Изузети домени" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden неће тражити да сачува податке за пријављивање за ове домене за све пријављене налоге. Морате освежити страницу да би промене ступиле на снагу." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Сајт $number$ (УРЛ)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Изузете промене домена су сачуване" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генериши име" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Немате дозволу да уређујете ову ставку" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Аутентификација" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 84de9bbfa05..a443a8e6b2e 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -2324,6 +2324,9 @@ "message": "Domäner", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Exkluderade domäner" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Webbplats $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generera användarnamn" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 6ab3755c8f4..e34751eea7d 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index d9ff1c3f076..1b493de3d2c 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2324,6 +2324,9 @@ "message": "Domains", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Website $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Excluded domain changes saved" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Generate username" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index e1236b3f86d..8095a9f6045 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2324,6 +2324,9 @@ "message": "Alan adları", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Engellenen alan adları" + }, "excludedDomains": { "message": "Hariç tutulan alan adları" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden, oturum açmış tüm hesaplar için bu alan adlarının hesap bilgilerini kaydetmeyi sormayacaktır. Değişikliklerin etkili olması için sayfayı yenilemeniz gerekir." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Web sitesi $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Engelli alan adı değişiklikleri kaydedildi" + }, "excludedDomainsSavedSuccess": { "message": "Alan adı istisnası değişiklikleri kaydedildi" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Kullanıcı adı oluştur" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Bu kaydı düzenleme yetkisine sahip değilsiniz" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Kimlik doğrulanıyor" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 8e20bc56ff5..d6b0b88ead2 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2324,6 +2324,9 @@ "message": "Домени", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Заблоковані домени" + }, "excludedDomains": { "message": "Виключені домени" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden не запитуватиме про збереження даних входу для цих доменів для всіх облікових записів, до яких виконано вхід. Потрібно оновити сторінку для застосування змін." }, + "blockedDomainsDesc": { + "message": "Автозаповнення та інші пов'язані функції не пропонуватимуться для цих вебсайтів. Вам слід оновити сторінку для застосування змін." + }, + "autofillBlockedNotice": { + "message": "Автозаповнення заблоковано для цього вебсайту. Перегляньте або змініть це в налаштуваннях." + }, + "autofillBlockedTooltip": { + "message": "Автозаповнення заблоковано на цьому вебсайті. Перевірте налаштування." + }, "websiteItemLabel": { "message": "Вебсайт $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Зміни заблокованих доменів збережено" + }, "excludedDomainsSavedSuccess": { "message": "Виняток для домену збережено" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Генерувати ім'я користувача" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "Вам не дозволено редагувати цей запис" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Аутентифікація" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 268b12a6254..4ccdaf808f3 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2324,6 +2324,9 @@ "message": "Các tên miền", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "Blocked domains" + }, "excludedDomains": { "message": "Tên miền đã loại trừ" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden sẽ không yêu cầu lưu thông tin đăng nhập cho các miền này. Bạn phải làm mới trang để các thay đổi có hiệu lực." }, + "blockedDomainsDesc": { + "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + }, + "autofillBlockedNotice": { + "message": "Autofill is blocked for this website. Review or change this in settings." + }, + "autofillBlockedTooltip": { + "message": "Autofill is blocked on this website. Review in settings." + }, "websiteItemLabel": { "message": "Trang Web $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "Blocked domain changes saved" + }, "excludedDomainsSavedSuccess": { "message": "Các thay đổi tên miền loại trừ đã được lưu" }, @@ -2789,6 +2804,20 @@ "error": { "message": "Lỗi" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "Tạo tên người dùng" }, @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "You don't have permission to edit this item" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 69c1194af47..dd6a2286c4a 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", + "message": "无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -290,7 +290,7 @@ "message": "前往帮助中心吗?" }, "continueToHelpCenterDesc": { - "message": "访问帮助中心了解更多如何使用 Bitwarden 的信息。" + "message": "在帮助中心进一步了解如何使用 Bitwarden。" }, "continueToBrowserExtensionStore": { "message": "前往浏览器扩展商店吗?" @@ -834,7 +834,7 @@ "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来截取此网站的验证器二维码,或者手动复制并粘贴密钥到此字段。" }, "learnMoreAboutAuthenticators": { - "message": "了解更多关于验证器的信息" + "message": "进一步了解验证器" }, "copyTOTP": { "message": "复制验证器密钥 (TOTP)" @@ -855,13 +855,13 @@ "message": "登录到 Bitwarden" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -1514,10 +1514,10 @@ "message": "不完整或不信任的网站可以利用页面加载时的自动填充功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "了解更多关于风险的信息" + "message": "进一步了解风险" }, "learnMoreAboutAutofill": { - "message": "了解更多关于自动填充的信息" + "message": "进一步了解自动填充" }, "defaultAutoFillOnPageLoad": { "message": "登录项目的默认自动填充设置" @@ -2318,12 +2318,15 @@ "message": "一个组织策略正影响您的所有权选项。" }, "personalOwnershipPolicyInEffectImports": { - "message": "组织策略已阻止将项目导入您的个人密码库。" + "message": "某个组织策略已阻止将项目导入您的个人密码库。" }, "domainsTitle": { "message": "域名", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "屏蔽域名" + }, "excludedDomains": { "message": "排除域名" }, @@ -2331,7 +2334,16 @@ "message": "Bitwarden 将不会询问是否为这些域名保存登录信息。您必须刷新页面才能使更改生效。" }, "excludedDomainsDescAlt": { - "message": "Bitwarden 不会询问保存所有已登录的账户的这些域名的登录信息。必须刷新页面才能使更改生效。" + "message": "Bitwarden 将不会询问是否为所有已登录账户的这些域名保存登录信息。您必须刷新页面才能使更改生效。" + }, + "blockedDomainsDesc": { + "message": "将不会为这些网站提供自动填充和其他相关功能。您必须刷新页面才能使更改生效。" + }, + "autofillBlockedNotice": { + "message": "该网站的自动填充功能已被阻止。请在设置中查看或更改。" + }, + "autofillBlockedTooltip": { + "message": "该网站的自动填充功能已被阻止。请在设置中查看。" }, "websiteItemLabel": { "message": "网站 $number$ (URI)", @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "屏蔽域名更改已保存" + }, "excludedDomainsSavedSuccess": { "message": "排除域名更改已保存" }, @@ -2601,7 +2616,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2789,6 +2804,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "生成用户名" }, @@ -2810,7 +2839,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2820,7 +2849,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3413,11 +3442,11 @@ "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "导入中...", + "message": "正在导入...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "数据导入成功!", + "message": "数据成功导入!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { @@ -3926,7 +3955,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "将 Bitwarden 设置为您的默认密码管理器吗?", + "message": "将 Bitwarden 设置为默认密码管理器吗?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { @@ -3934,7 +3963,7 @@ "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "将 Bitwarden 设置为您的默认密码管理器", + "message": "将 Bitwarden 设置为默认密码管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { @@ -3991,7 +4020,7 @@ "message": "没有搜索到匹配的项目" }, "clearFiltersOrTryAnother": { - "message": "清除筛选器或尝试另一个搜索词" + "message": "清除筛选或尝试另一个搜索词" }, "copyInfoTitle": { "message": "复制信息 - $ITEMNAME$", @@ -4567,7 +4596,7 @@ "message": "账户操作" }, "showNumberOfAutofillSuggestions": { - "message": "在扩展图标上显示自动填充建议的登录的数量" + "message": "在扩展图标上显示自动填充建议的登录数量" }, "showQuickCopyActions": { "message": "在密码库上显示快速复制操作" @@ -4641,6 +4670,33 @@ "noEditPermissions": { "message": "您没有编辑此项目的权限" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "生物识别解锁不可用,因为 Bitwarden 桌面 App 已关闭。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, "authenticating": { "message": "正在验证" }, @@ -4819,7 +4875,7 @@ "message": "稍后提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "您能可正常访问您的电子邮箱 $EMAIL$ 吗?", + "message": "您可以正常访问您的电子邮箱 $EMAIL$ 吗?", "placeholders": { "email": { "content": "$1", diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 4b215df6ba2..467deffd815 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -20,7 +20,7 @@ "message": "建立帳戶" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "第一次使用 Bitwarden?" }, "logInWithPasskey": { "message": "使用密碼金鑰登入" @@ -29,7 +29,7 @@ "message": "使用單一登入" }, "welcomeBack": { - "message": "Welcome back" + "message": "歡迎回來" }, "setAStrongPassword": { "message": "設定一個強密碼" @@ -84,7 +84,7 @@ "message": "加入組織" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "加入 $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -120,7 +120,7 @@ "message": "複製密碼" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "複製密碼短語" }, "copyNote": { "message": "複製備註" @@ -153,13 +153,13 @@ "message": "複製駕照號碼" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "複製私密金鑰" }, "copyPublicKey": { - "message": "Copy public key" + "message": "複製公開金鑰" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "複製指紋" }, "copyCustomField": { "message": "複製 $FIELD$", @@ -177,7 +177,7 @@ "message": "複製備註" }, "fill": { - "message": "Fill", + "message": "填入", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -193,10 +193,10 @@ "message": "自動填入身分資訊" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "填入驗證碼" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "填入驗證碼", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -337,22 +337,22 @@ "message": "您可以使用 Bitwarden 驗證器儲存驗證器金鑰,並為兩步驟驗證流程產生 TOTP 代碼。前往 bitwarden.com 網站以了解更多資訊。" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Bitwarden 密鑰管理" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "使用 Bitwarden 密鑰管理來安全儲存、管理並分享開發人員密鑰。在 bitwarden.com 網站上了解更多。" }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "使用 Passwordless.dev 建立流暢且安全的登入體驗,無需使用傳統密碼。在 bitwarden 網站上了解更多。" }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "免費的 Bitwarden 家庭方案" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "您符合免費 Bitwarden 家庭方案的資格要求。在網頁應用程式中兌換您的優惠。" }, "version": { "message": "版本" @@ -379,16 +379,16 @@ "message": "資料夾名稱" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "在資料夾名稱後面使用「/」來建立樹狀結構。\n例如:社交網路/論壇" }, "noFoldersAdded": { - "message": "No folders added" + "message": "未新增資料夾" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "建立資料夾來管理您的密碼庫項目" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "您確定要永久刪除此資料夾嗎?" }, "deleteFolder": { "message": "刪除資料夾" @@ -431,7 +431,7 @@ "message": "自動產生安全、唯一的登入密碼。" }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden 網頁應用程式" }, "importItems": { "message": "匯入項目" @@ -443,7 +443,7 @@ "message": "產生密碼" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "產生密碼短語" }, "regeneratePassword": { "message": "重新產生密碼" @@ -475,7 +475,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "包含大寫字元", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -483,7 +483,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -491,7 +491,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "包含數字", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -499,7 +499,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "包含特殊字元", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -526,11 +526,11 @@ "message": "最少符號位數" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "避免易混淆的字元", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "企業原則之要求已在你的產生器選項中生效", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -564,19 +564,19 @@ "message": "我的最愛" }, "unfavorite": { - "message": "Unfavorite" + "message": "取消最愛" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "項目已加入到最愛" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "項目已從最愛中移除" }, "notes": { "message": "備註" }, "privateNote": { - "message": "Private note" + "message": "私人備註" }, "note": { "message": "備註" @@ -600,7 +600,7 @@ "message": "開啟網站" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "開啟網站 $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -651,7 +651,7 @@ "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "您的密碼庫已被鎖定" }, "yourAccountIsLocked": { "message": "您的帳戶已被鎖定。" @@ -742,7 +742,7 @@ "message": "主密碼" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "若您忘記主密碼,將會無法找回!" }, "masterPassHintLabel": { "message": "您已成功創建新帳戶!" @@ -782,7 +782,7 @@ "message": "您已成功創建新帳戶!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "你已經登入!" }, "youSuccessfullyLoggedIn": { "message": "登入成功" @@ -797,7 +797,7 @@ "message": "必須填入驗證碼。" }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "驗證已被取消或時間超過。請再試一次。" }, "invalidVerificationCode": { "message": "無效的驗證碼" @@ -834,7 +834,7 @@ "message": "Bitwarden 可以儲存並填入兩步驟驗證碼。選擇相機圖示來截取此網站的驗證器QR code,或手動複製金鑰並貼上到此欄位。" }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "了解更多驗證程式" }, "copyTOTP": { "message": "複製驗證器金鑰 (TOTP)" @@ -852,19 +852,19 @@ "message": "登入" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "登入 Bitwarden" }, "restartRegistration": { - "message": "Restart registration" + "message": "重新啟動註冊" }, "expiredLink": { - "message": "Expired link" + "message": "過期連結" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "請重新啟動註冊流程或是重試登入。" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "您可能已經有帳號" }, "logOutConfirmation": { "message": "您確定要登出嗎?" @@ -888,7 +888,7 @@ "message": "兩步驟登入需要您從其他裝置(例如安全鑰匙、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這使您的帳戶更加安全。兩步驟登入可以在 bitwarden.com 網頁版密碼庫啟用。現在要前往嗎?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "在 Bitwarden 網頁應用程式中設定兩步驟登入,讓您的帳號更加安全。" }, "twoStepLoginConfirmationTitle": { "message": "前往網頁 App 嗎?" @@ -934,7 +934,7 @@ "message": "新增 URI" }, "addDomain": { - "message": "Add domain", + "message": "新增網域", "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." }, "addedItem": { @@ -1005,7 +1005,7 @@ "message": "於分頁頁面顯示身分以便於自動填入。" }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "在密碼庫檢視中點擊項目來自動填入" }, "clearClipboard": { "message": "清除剪貼簿", @@ -1317,10 +1317,10 @@ "message": "輸入驗證器應用程式提供的 6 位數驗證碼。" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "驗證工作階段因時間過久已逾時。請重試登入。" }, "enterVerificationCodeEmail": { "message": "輸入已傳送至 $EMAIL$ 的 6 位數驗證碼。", @@ -1386,17 +1386,17 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1413,7 +1413,7 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "selfHostedEnvironment": { "message": "自我裝載環境" @@ -1422,13 +1422,13 @@ "message": "指定您內部部署的 Bitwarden 安裝之基礎 URL。" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 URL。例如:https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "適用於進階設定。您可以單獨指定各個服務的網域 URL。" }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" @@ -1440,7 +1440,7 @@ "message": "伺服器 URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1466,22 +1466,22 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "自動填入建議" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "在表單欄位上顯示自動填入選單" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "顯示身分建議" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "顯示信用卡建議" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "當選擇圖示時,顯示建議" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "適用於所有已登入的帳戶。" }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "關閉你的瀏覽器內建密碼管理器設定以避免衝突。" @@ -1502,7 +1502,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "頁面載入時自動填入" }, "enableAutoFillOnPageLoad": { "message": "頁面載入時自動填入" @@ -1514,7 +1514,7 @@ "message": "被入侵或不被信任的網站,可能會濫用頁面載入的自動填入功能。" }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "了解更多風險" }, "learnMoreAboutAutofill": { "message": "進一步瞭解「自動填入」功能" @@ -1544,13 +1544,13 @@ "message": "在側邊欄中開啟密碼庫" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "自動將上次使用的登入資料填入目前網站" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "自動將上次使用的信用卡填入目前網站" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "自動將上次使用的身分資料填入目前網站" }, "commandGeneratePasswordDesc": { "message": "產生一組新的隨機密碼並將它複製到剪貼簿中。" @@ -1768,7 +1768,7 @@ "message": "身分" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "newItemHeader": { "message": "新增 $TYPE$", @@ -1801,13 +1801,13 @@ "message": "密碼歷史記錄" }, "generatorHistory": { - "message": "Generator history" + "message": "產生器歷史記錄" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "清除產生器歷史記錄" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "若繼續,所有產生器曾經產生的記錄會被刪除。您確定要繼續?" }, "back": { "message": "返回" @@ -1846,7 +1846,7 @@ "message": "安全筆記" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH 金鑰" }, "clear": { "message": "清除", @@ -1929,10 +1929,10 @@ "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "您最近未產生任何密碼" }, "remove": { "message": "移除" @@ -2002,7 +2002,7 @@ "message": "設定您用來解鎖 Bitwarden 的 PIN 碼。您的 PIN 設定將在您完全登出本應用程式時被重設。" }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "您的 PIN 碼會取代主密碼用來解鎖 Bitwarden。您的 PIN 碼會重置,若您完全登出 Bitwarden。" }, "pinRequired": { "message": "必須填入 PIN 碼。" @@ -2017,7 +2017,7 @@ "message": "使用生物特徵辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "awaitDesktop": { "message": "等待來自桌面應用程式的確認" @@ -2029,7 +2029,7 @@ "message": "瀏覽器重啟後使用主密碼鎖定" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "瀏覽器重啟後使用主密碼鎖定" }, "selectOneCollection": { "message": "您必須至少選擇一個集合。" @@ -2041,26 +2041,26 @@ "message": "克隆" }, "passwordGenerator": { - "message": "Password generator" + "message": "密碼產生器" }, "usernameGenerator": { - "message": "Username generator" + "message": "使用者名稱產生器" }, "useThisPassword": { - "message": "Use this password" + "message": "使用此密碼" }, "useThisUsername": { - "message": "Use this username" + "message": "使用此使用者名稱" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "已產生安全的密碼!請不要忘記同時更新您網站上的密碼。" }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "使用產生器", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "來產生高強度且唯一的密碼", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultTimeoutAction": { @@ -2096,7 +2096,7 @@ "message": "項目已還原" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "已經有帳號了嗎?" }, "vaultTimeoutLogOutConfirmation": { "message": "選擇登出將會在密碼庫逾時後移除對密碼庫的所有存取權限,若要重新驗證則需連線網路。確定要使用此設定嗎?" @@ -2108,7 +2108,7 @@ "message": "自動填入並儲存" }, "fillAndSave": { - "message": "Fill and save" + "message": "填入並儲存" }, "autoFillSuccessAndSavedUri": { "message": "項目已自動填入並且已儲存統一資源標識符(URI)" @@ -2189,19 +2189,19 @@ "message": "您新的主密碼不符合原則要求。" }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "獲得來自 Bitwarden 的公告、建議及研究資訊電子郵件。" }, "unsubscribe": { - "message": "Unsubscribe" + "message": "取消訂閱" }, "atAnyTime": { - "message": "at any time." + "message": "在任何時間。" }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "若是繼續,則代表您同意" }, "and": { - "message": "and" + "message": "和" }, "acceptPolicies": { "message": "選中此選取框,即表示您同意下列條款:" @@ -2222,10 +2222,10 @@ "message": "確定" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "存取權杖更新失敗" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "未找到存取權杖或 API 密鑰。請重試登出再登入。" }, "desktopSyncVerificationTitle": { "message": "桌面同步驗證" @@ -2264,10 +2264,10 @@ "message": "帳戶不相符" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "生物辨識金鑰錯誤" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "生物辨識解鎖失敗。生物辨識金鑰解鎖密碼庫失敗。請嘗試重新設定生物辨識。" }, "biometricsNotEnabledTitle": { "message": "生物特徵辨識未設定" @@ -2282,16 +2282,16 @@ "message": "此裝置不支援瀏覽器生物特徵辨識。" }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "使用者已鎖定或登出" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "請在桌面應用程式解鎖此使用者之後再重試。" }, "biometricsNotAvailableTitle": { "message": "生物辨識解鎖不可用" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "生物辨識解鎖現在無法使用。請稍後重試。" }, "biometricsFailedTitle": { "message": "生物特徵辨識失敗" @@ -2324,6 +2324,9 @@ "message": "網域", "description": "A category title describing the concept of web domains" }, + "blockedDomains": { + "message": "已封鎖的網域" + }, "excludedDomains": { "message": "排除網域" }, @@ -2333,6 +2336,15 @@ "excludedDomainsDescAlt": { "message": "對於所有已登入的帳戶,Bitwarden 不會詢問是否儲存這些網域的登入資訊。您必須重新整理頁面變更才會生效。" }, + "blockedDomainsDesc": { + "message": "自動填入及其它相關的功能無法在這些網站上使用。您必須重新整理頁面來使變更生效。" + }, + "autofillBlockedNotice": { + "message": "自動填入已在此網站被封鎖。請在設定中檢視或更改此限制。" + }, + "autofillBlockedTooltip": { + "message": "自動填入已在此網站被封鎖。請在設定中檢視。" + }, "websiteItemLabel": { "message": "網站 $number$ (URI)", "placeholders": { @@ -2351,6 +2363,9 @@ } } }, + "blockedDomainsSavedSuccess": { + "message": "已儲存封鎖的網域" + }, "excludedDomainsSavedSuccess": { "message": "例外網域更改已儲存" }, @@ -2789,6 +2804,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "來避免更多資料遺失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "generateUsername": { "message": "產生使用者名稱" }, @@ -2887,7 +2916,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "網站:$WEBSITE$。透過 Bitwarden 產生。", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2897,7 +2926,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "無效的 $SERVICENAME$ API 權杖", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2907,7 +2936,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "無效的 $SERVICENAME$ API 權杖:$ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2921,7 +2950,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "無法獲得 $SERVICENAME$ 的轉送電子郵件帳號。", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2931,7 +2960,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "無效的 $SERVICENAME$ 網域。", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2941,7 +2970,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "無效的 $SERVICENAME$ URI。", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2951,7 +2980,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "發生未知的 $SERVICENAME$ 錯誤。", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2961,7 +2990,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "未知的轉送服務提供商:$SERVICENAME$。", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3068,25 +3097,25 @@ "message": "重新傳送通知" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "檢視所有登入選項" }, "viewAllLoginOptionsV1": { - "message": "View all log in options" + "message": "檢視所有登入選項" }, "notificationSentDevice": { "message": "已傳送通知至您的裝置。" }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "已傳送通知至您的裝置" }, "makeSureYourAccountIsUnlockedAndTheFingerprintEtc": { - "message": "Make sure your account is unlocked and the fingerprint phrase matches on the other device" + "message": "請確保您的帳號已解鎖,並且指紋短語與其他裝置一致。" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "一旦您的請求被通過,您會獲得通知。" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "需要另一個選項嗎?" }, "loginInitiated": { "message": "登入已啟動" @@ -3146,22 +3175,22 @@ "message": "自動填入設定" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "自動填入快速鍵" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "修改鍵盤快速鍵" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "管理鍵盤快速鍵" }, "autofillShortcut": { "message": "自動填入鍵盤快速鍵" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "自動填入快速鍵尚未設定。請在瀏覽器的設定中變更。" }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "自動填入快速鍵是 $COMMAND$。請在瀏覽器的設定中變更。", "placeholders": { "command": { "content": "$1", @@ -3182,16 +3211,16 @@ "message": "在新視窗開啟" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "記住此裝置來讓未來的登入體驗更簡易" }, "deviceApprovalRequired": { "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "需要核准裝置" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "選擇下面的一個核准選項" }, "rememberThisDevice": { "message": "記住這個裝置" @@ -3212,25 +3241,25 @@ "message": "需要組織 SSO 識別碼。" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "建立帳號於" }, "checkYourEmail": { - "message": "Check your email" + "message": "檢查您的電子郵件" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "跟隨電子郵件中的連結" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "並繼續建立您的帳號" }, "noEmail": { - "message": "No email?" + "message": "沒有電子郵件?" }, "goBack": { - "message": "Go back" + "message": "返回" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "來編輯您的電子郵件位址。" }, "eu": { "message": "歐盟", @@ -3267,17 +3296,17 @@ "message": "缺少使用者電子郵件地址" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "未找到使用中帳號的電子郵件。正在將您登出。" }, "deviceTrusted": { "message": "裝置已信任" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "沒有可用的 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "使用 Send 可以與任何人安全地共用加密資訊。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3354,10 +3383,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "您需注意上方的 1 個欄位。" }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "您需注意上方的 $COUNT$ 個欄位。", "placeholders": { "count": { "content": "$1", @@ -3444,7 +3473,7 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -3466,7 +3495,7 @@ "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "解鎖您的帳號來查看建議的自動填入", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -3474,15 +3503,15 @@ "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "解鎖您的帳號,並開啟在新視窗", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "基於時間的一次性驗證碼", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "現在的 TOTP 到期剩餘時間", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { @@ -3510,23 +3539,23 @@ "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "新增新的密碼庫登入項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "新信用卡", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "新增新的密碼庫信用卡項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "新身份識別", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "新增新的密碼庫身分項目,在新視窗中顯示", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3616,19 +3645,19 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "彈出擴充套件視窗來完成登入。" }, "popoutExtension": { - "message": "Popout extension" + "message": "彈出擴充套件視窗" }, "launchDuo": { "message": "開啟Duo" @@ -3646,7 +3675,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3705,16 +3734,16 @@ "message": "確認檔案密碼" }, "exportSuccess": { - "message": "Vault data exported" + "message": "密碼庫資料已匯出" }, "typePasskey": { "message": "密碼金鑰" }, "accessing": { - "message": "Accessing" + "message": "正在存取" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "已登入!" }, "passkeyNotCopied": { "message": "密碼金鑰不會被複製" @@ -3738,7 +3767,7 @@ "message": "您沒有符合該網站的登入資訊。" }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "未找到此網站的登入資訊" }, "searchSavePasskeyNewLogin": { "message": "搜尋或將密碼金鑰儲存為新的登入資訊" @@ -3877,19 +3906,19 @@ "message": "伺服器" }, "hostedAt": { - "message": "hosted at" + "message": "架設在" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "使用您的裝置或密碼金鑰" }, "justOnce": { "message": "僅此一次" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "永遠針對此網站" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "$DOMAIN$ 已新增到排除的網域。", "placeholders": { "domain": { "content": "$1", @@ -3898,51 +3927,51 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "常見格式", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "繼續前往瀏覽器設定?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "繼續前往說明中心?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "在您瀏覽器的偏好設定中更改自動填入及密碼管理。", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "您可以在您瀏覽器的偏好設定中檢視及設定擴充套件的快速鍵。", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "在您瀏覽器的偏好設定中更改自動填入及密碼管理。", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "您可以在您瀏覽器的偏好設定中檢視及設定擴充套件的快速鍵。", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "使用 Bitwarden 作為預設的密碼管理器?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "忽略此設定可能會導致 Bitwarden 自動填入選單與您的瀏覽器產生衝突。", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "使用 Bitwarden 作為預設的密碼管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "無法設定 Bitwarden 作為預設的密碼管理器", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "您必須同意您瀏覽器的隱私權限設定來設定 Bitwarden 為預設的密碼管理器。", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { @@ -3950,19 +3979,19 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "憑證資訊成功儲存!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "密碼已儲存!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "憑證資訊成功更新!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "密碼已更新!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -3970,7 +3999,7 @@ "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "成功" }, "removePasskey": { "message": "移除密碼金鑰" @@ -3979,19 +4008,19 @@ "message": "密碼金鑰已移除" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "自動填入建議" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "對此網站儲存登入項目為自動填入" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "您的密碼庫是空的" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "沒有找到相符的項目。" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "清除過濾器或更換另一個搜尋條件" }, "copyInfoTitle": { "message": "複製資訊 - $ITEMNAME$", @@ -4004,7 +4033,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "複製備註 - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4034,7 +4063,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "檢視項目 - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4044,7 +4073,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "自動填入 - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4054,22 +4083,22 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "沒有資料可以複製" }, "assignToCollections": { "message": "指派至集合" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copyPhone": { - "message": "Copy phone" + "message": "複製電話" }, "copyAddress": { - "message": "Copy address" + "message": "複製地址" }, "adminConsole": { - "message": "Admin Console" + "message": "管理控制台" }, "accountSecurity": { "message": "帳戶安全性" @@ -4081,13 +4110,13 @@ "message": "外觀" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "指定目標集合時發生錯誤。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "指定目標資料夾時發生錯誤。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "檢視 $NAME$ 中的項目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4097,7 +4126,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4107,7 +4136,7 @@ } }, "new": { - "message": "New" + "message": "新增" }, "removeItem": { "message": "移除 $NAME$", @@ -4120,16 +4149,16 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "不在任何資料夾中的項目" }, "itemDetails": { "message": "項目詳細資訊" }, "itemName": { - "message": "Item name" + "message": "項目名稱" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "若您只有檢視權限,無法移除集合 $COLLECTIONS$。", "placeholders": { "collections": { "content": "$1", @@ -4138,47 +4167,47 @@ } }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "組織已被停用" }, "owner": { - "message": "Owner" + "message": "擁有者" }, "selfOwnershipLabel": { - "message": "You", + "message": "您", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "無法存取已停用組織中的項目。請聯絡您組織的擁有者以獲取協助。" }, "additionalInformation": { - "message": "Additional information" + "message": "更多資訊" }, "itemHistory": { - "message": "Item history" + "message": "項目歷史記錄" }, "lastEdited": { - "message": "Last edited" + "message": "最後編輯" }, "ownerYou": { - "message": "Owner: You" + "message": "擁有者: 您" }, "linked": { - "message": "Linked" + "message": "連結" }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "upload": { - "message": "Upload" + "message": "上傳" }, "addAttachment": { - "message": "Add attachment" + "message": "新增附件" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "最大檔案大小為 500MB" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "刪除附檔 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4187,7 +4216,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "下載 $NAME$", "placeholders": { "name": { "content": "$1", @@ -4196,25 +4225,25 @@ } }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "您確定要永久刪除此附檔嗎?" }, "premium": { "message": "進階版" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "免費組織無法使用附檔" }, "filters": { - "message": "Filters" + "message": "篩選器" }, "filterVault": { - "message": "Filter vault" + "message": "過濾密碼庫" }, "filterApplied": { - "message": "One filter applied" + "message": "套用了一個過濾條件" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "套用了 $COUNT$ 個過濾條件", "placeholders": { "count": { "content": "$1", @@ -4226,13 +4255,13 @@ "message": "個人資訊" }, "identification": { - "message": "Identification" + "message": "身份" }, "contactInfo": { - "message": "Contact info" + "message": "聯繫資訊" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "下載 - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4241,23 +4270,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "以此號碼結尾的信用卡", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "登入資訊" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "驗證器金鑰" }, "autofillOptions": { "message": "自動填入選項" }, "websiteUri": { - "message": "Website (URI)" + "message": "網站 (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "網站 (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4267,16 +4296,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "網站已新增" }, "addWebsite": { - "message": "Add website" + "message": "新增網站" }, "deleteWebsite": { - "message": "Delete website" + "message": "刪除網站" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "預設 ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4286,7 +4315,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "顯示偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4295,7 +4324,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "隱藏偵測到的吻合 $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4304,13 +4333,13 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "在頁面載入時自動填寫?" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "過期的信用卡" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "如果您已續期,請更新信用卡資訊" }, "cardDetails": { "message": "信用卡詳細資料" @@ -4334,17 +4363,17 @@ "message": "新增帳戶" }, "loading": { - "message": "Loading" + "message": "正在載入" }, "data": { - "message": "Data" + "message": "資料" }, "passkeys": { "message": "密碼金鑰", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "密碼", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { @@ -4352,16 +4381,16 @@ "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "指定" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "只有可以檢視集合的組織成員才能看到其中的項目。" }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "只有可以檢視集合的組織成員才能看到其中的項目。" }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "您已經選擇 $TOTAL_COUNT$ 個項目。由於您沒有編輯權限,無法更新其中的 $READONLY_COUNT$ 個項目。", "placeholders": { "total_count": { "content": "$1", @@ -4373,37 +4402,37 @@ } }, "addField": { - "message": "Add field" + "message": "新增欄位" }, "add": { - "message": "Add" + "message": "新增" }, "fieldType": { - "message": "Field type" + "message": "欄位類型" }, "fieldLabel": { - "message": "Field label" + "message": "欄位標籤" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "像是安全問答的資訊可以使用文字欄位來儲存" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "像是密碼的機密資訊可以使用隱藏欄位來儲存" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "若您想自動填入欄位中的核取方塊,例如儲存電子郵件,可以使用核取方塊。" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "使用連結欄位若您在特定網站上遇到自動填入問題。" }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "填入欄位的 html id、名稱、標籤或預留字元" }, "editField": { - "message": "Edit field" + "message": "編輯欄位" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "編輯 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4412,7 +4441,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "刪除 $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4421,7 +4450,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ 已新增", "placeholders": { "label": { "content": "$1", @@ -4430,7 +4459,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "重新排序 $LABEL$。使用方向鍵來往上或下移動。", "placeholders": { "label": { "content": "$1", @@ -4439,7 +4468,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "往上移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4459,10 +4488,10 @@ "message": "選擇要指派的集合" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 個項目會被永久移到選擇的組織。您將不再擁有此項目。" }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ 個項目會被永久移到選擇的組織。您將不再擁有這些項目。", "placeholders": { "personal_items_count": { "content": "$1", @@ -4471,7 +4500,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 個項目會被永久移到 $ORG$。您將不再擁有此項目。", "placeholders": { "org": { "content": "$1", @@ -4480,7 +4509,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ 個項目會被永久移到 $ORG$。您將不再擁有這些項目。", "placeholders": { "personal_items_count": { "content": "$1", @@ -4496,10 +4525,10 @@ "message": "指派集合成功" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "您沒有選擇任何項目。" }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "將已選取項目移動至 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4508,7 +4537,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4517,7 +4546,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "項目已移到 $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -4526,7 +4555,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "往下移動 $LABEL$,位置 $LENGTH$ 之 $INDEX$", "placeholders": { "label": { "content": "$1", @@ -4543,52 +4572,52 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "項目位置" }, "fileSend": { - "message": "File Send" + "message": "檔案 Send" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSend": { - "message": "Text Send" + "message": "文字 Send" }, "textSends": { - "message": "Text Sends" + "message": "文字 Sends" }, "bitwardenNewLook": { - "message": "Bitwarden has a new look!" + "message": "Bitwarden 有了新外觀!" }, "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" + "message": "更容易使用的自動填入及密碼庫搜尋體驗。試試看吧!" }, "accountActions": { - "message": "Account actions" + "message": "帳號動作" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "在擴充套件圖示上顯示自動填入建議的數量" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "在密碼庫中顯示快速複製" }, "systemDefault": { - "message": "System default" + "message": "系統預設值" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "企業政策已套用至您的選項中" }, "sshPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshPublicKey": { - "message": "Public key" + "message": "公共金鑰" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "指紋" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "金鑰類型" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4603,223 +4632,250 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "重試" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "自訂逾時時間最小為 1 分鐘。" }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "以及更多內容" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "檔案已儲存到裝置。在您的裝置上管理下載檔案。" }, "showCharacterCount": { - "message": "Show character count" + "message": "顯示字元數" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "隱藏字元數" }, "itemsInTrash": { - "message": "Items in trash" + "message": "在垃圾桶中的項目" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "垃圾桶中沒有項目" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "您刪除的項目會在此顯示,並會在 30 天之後永久刪除" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "垃圾桶中超過 30 天的項目將會被自動刪除。" }, "restore": { - "message": "Restore" + "message": "還原" }, "deleteForever": { - "message": "Delete forever" + "message": "永遠刪除" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "你沒有權限編輯這個項目" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextDesktopDisconnected": { + "message": "由於 Bitwarden 桌面應用程式已關閉,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledInDesktop": { + "message": "由於未 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "基於不明原因,生物辨識解鎖無法使用。" }, "authenticating": { - "message": "Authenticating" + "message": "驗證中" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "自動填入產生的密碼", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "密碼已重新產生", "description": "Notification message for when a password has been regenerated" }, "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "message": "在 Bitwarden 中儲存登入資訊?", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "空格", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "波浪", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "重音符", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "驚歎號", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "在符號", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "井字號", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "錢字號", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "百分比", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "插入號", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "和符號", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "星號", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "左括號", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "右括號", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "底線", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "連字號", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "加號", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "等號", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "左大括號", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "右大括號", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "左中括號", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "右中括號", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "垂直符號", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "反斜線", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "冒號", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "分號", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "雙引號", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "單引號", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "小於", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "大於", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "逗號", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "句號", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "問號", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "斜線", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "小寫" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "大寫" }, "generatedPassword": { - "message": "Generated password" + "message": "已產生的密碼" }, "compactMode": { - "message": "Compact mode" + "message": "緊湊模式" }, "beta": { - "message": "Beta" + "message": "測試版" }, "importantNotice": { - "message": "Important notice" + "message": "重要通知" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "啟動兩階段登入" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", "placeholders": { "email": { "content": "$1", @@ -4828,24 +4884,24 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "不,我不行" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "是,我可以存取我的電子郵件位址" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" }, "extensionWidth": { - "message": "Extension width" + "message": "擴充套件寬度" }, "wide": { - "message": "Wide" + "message": "寬度" }, "extraWide": { - "message": "Extra wide" + "message": "更寬" } } diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index ad7e6f67361..841eefda0ad 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -25,6 +25,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { UserId } from "@bitwarden/common/types/guid"; import { ButtonModule, I18nMockService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RegistrationCheckEmailIcon } from "../../../../../../libs/auth/src/angular/icons/registration-check-email.icon"; import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; import { AccountSwitcherService } from "../account-switching/services/account-switcher.service"; diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html index 3f874fc1a76..0f2754b2bf2 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -109,18 +109,6 @@

{{ "otherOptions" | i18n }}

- - - {{ "lockNow" | i18n }} - - - - {{ "logOut" | i18n }} - diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 099f445be85..7e094fe508b 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -63,7 +63,6 @@ import { import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -107,7 +106,6 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { hasVaultTimeoutPolicy = false; biometricUnavailabilityReason: string; showChangeMasterPass = true; - accountSwitcherEnabled = false; form = this.formBuilder.group({ vaultTimeout: [null as VaultTimeout | null], @@ -140,9 +138,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private toastService: ToastService, private biometricsService: BiometricsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } + ) {} async ngOnInit() { const hasMasterPassword = await this.userVerificationService.hasMasterPassword(); @@ -210,6 +206,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { switchMap(async () => { const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id); const biometricSettingAvailable = + !(await BrowserApi.permissionsGranted(["nativeMessaging"])) || (status !== BiometricsStatus.DesktopDisconnected && status !== BiometricsStatus.NotEnabledInConnectedDesktopApp) || (await this.vaultTimeoutSettingsService.isBiometricLockSet()); @@ -510,7 +507,18 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { const biometricsPromise = async () => { try { - const result = await this.biometricsService.authenticateWithBiometrics(); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a.id)), + ); + let result = false; + try { + const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); + result = await this.keyService.validateUserKey(userKey, userId); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + result = false; + } // prevent duplicate dialog biometricsResponseReceived = true; diff --git a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts index 0917b2703cf..53aedc7a5f3 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts @@ -13,11 +13,23 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; diff --git a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts index b6211bba05f..723152adfab 100644 --- a/apps/browser/src/auth/popup/two-factor-auth-email.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth-email.component.ts @@ -6,12 +6,26 @@ import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { TwoFactorAuthEmailComponent as TwoFactorAuthEmailBaseComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-email.component"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DialogService } from "../../../../../libs/components/src/dialog"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../../libs/components/src/typography"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; diff --git a/apps/browser/src/auth/popup/two-factor-auth.component.ts b/apps/browser/src/auth/popup/two-factor-auth.component.ts index 3cb82118597..f22bbbe202c 100644 --- a/apps/browser/src/auth/popup/two-factor-auth.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth.component.ts @@ -37,6 +37,8 @@ import { ToastService, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction, LoginEmailServiceAbstraction, diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 03284f3fd89..6ad9b8e06fd 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -57,6 +57,17 @@ export type InlineMenuElementPosition = { height: number; }; +export type FieldRect = { + bottom: number; + height: number; + left: number; + right: number; + top: number; + width: number; + x: number; + y: number; +}; + export type InlineMenuPosition = { button?: InlineMenuElementPosition; list?: InlineMenuElementPosition; @@ -134,6 +145,7 @@ export type OverlayBackgroundExtensionMessage = { isFieldCurrentlyFilling?: boolean; subFrameData?: SubFrameOffsetData; focusedFieldData?: FocusedFieldData; + allFieldsRect?: any; isOpeningFullInlineMenu?: boolean; styles?: Partial; data?: LockedVaultPendingNotificationsData; diff --git a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts index 73f936bb591..a300ac08660 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts @@ -453,12 +453,16 @@ describe("AutoSubmitLoginBackground", () => { sendMockExtensionMessage({ command: "triggerAutoSubmitLogin" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); it("skips acting on messages whose command does not have a registered handler", () => { sendMockExtensionMessage({ command: "someInvalidCommand" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 0ac69317855..c3a6357ed05 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -2913,6 +2913,124 @@ describe("OverlayBackground", () => { ); }); }); + describe("handles menu position when input is focused", () => { + it("sets button and menu width and position when non-multi-input totp field is focused", async () => { + const subframe = { + top: 0, + left: 0, + url: "", + frameId: 0, + }; + + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + focusedFieldRects: { + width: 49.328125, + height: 64, + top: 302.171875, + left: 1270.8125, + }, + }); + + const buttonPostion = overlayBackground["getInlineMenuButtonPosition"](subframe); + const menuPostion = overlayBackground["getInlineMenuListPosition"](subframe); + + expect(menuPostion).toEqual({ + width: "49px", + top: "366px", + left: "1271px", + }); + expect(buttonPostion).toEqual({ + width: "34px", + height: "34px", + top: "317px", + left: "1271px", + }); + }); + it("sets button and menu width and position when multi-input totp field is focused", async () => { + const subframe = { + top: 0, + left: 0, + url: "", + frameId: 0, + }; + + const totpFields = [ + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__0" }), + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__1" }), + createAutofillFieldMock({ autoCompleteType: "one-time-code", opid: "__2" }), + ]; + const allFieldData = [ + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__0", + rect: { + x: 1041.5, + y: 302.171875, + width: 49.328125, + height: 64, + top: 302.171875, + right: 1090.828125, + bottom: 366.171875, + left: 1041.5, + }, + }), + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__1", + rect: { + x: 1098.828125, + y: 302.171875, + width: 49.328125, + height: 64, + top: 302.171875, + right: 1148.15625, + bottom: 366.171875, + left: 1098.828125, + }, + }), + createAutofillFieldMock({ + autoCompleteType: "one-time-code", + opid: "__2", + rect: { + x: 1156.15625, + y: 302.171875, + width: 249.328125, + height: 64, + top: 302.171875, + right: 2205.484375, + bottom: 366.171875, + left: 2156.15625, + }, + }), + ]; + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + focusedFieldRects: { + width: 49.328125, + height: 64, + top: 302.171875, + left: 1270.8125, + }, + }); + + overlayBackground["allFieldData"] = allFieldData; + jest.spyOn(overlayBackground as any, "isTotpFieldForCurrentField").mockReturnValue(true); + jest.spyOn(overlayBackground as any, "getTotpFields").mockReturnValue(totpFields); + + const buttonPostion = overlayBackground["getInlineMenuButtonPosition"](subframe); + const menuPostion = overlayBackground["getInlineMenuListPosition"](subframe); + expect(menuPostion).toEqual({ + width: "1164px", + top: "366px", + left: "1042px", + }); + expect(buttonPostion).toEqual({ + width: "34px", + height: "34px", + top: "292px", + left: "2187px", + }); + }); + }); describe("triggerDelayedAutofillInlineMenuClosure message handler", () => { it("skips triggering the delayed closure of the inline menu if a field is currently focused", async () => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 58e462943bf..3d2b1ec783c 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -70,6 +70,7 @@ import { generateDomainMatchPatterns, generateRandomChars, isInvalidResponseStatusCode, + rectHasSize, specialCharacterToKeyMap, } from "../utils"; @@ -130,6 +131,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private currentInlineMenuCiphersCount: number = 0; private currentAddNewItemData: CurrentAddNewItemData; private focusedFieldData: FocusedFieldData; + private allFieldData: AutofillField[]; private isFieldCurrentlyFocused: boolean = false; private isFieldCurrentlyFilling: boolean = false; private isInlineMenuButtonVisible: boolean = false; @@ -1367,6 +1369,71 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.isInlineMenuListVisible = false; } + /** + * Get all the totp fields for the tab and frame of the currently focused field + */ + private getTotpFields(): AutofillField[] { + const currentTabId = this.focusedFieldData?.tabId; + const currentFrameId = this.focusedFieldData?.frameId; + const pageDetailsMap = this.pageDetailsForTab[currentTabId]; + const pageDetails = pageDetailsMap?.get(currentFrameId); + + const fields = pageDetails.details.fields; + const totpFields = fields.filter((f) => + this.inlineMenuFieldQualificationService.isTotpField(f), + ); + + return totpFields; + } + + /** + * calculates the postion and width for multi-input totp field inline menu + * @param totpFieldArray - the totp fields used to evaluate the position of the menu + */ + private calculateTotpMultiInputMenuBounds(totpFieldArray: AutofillField[]) { + // Filter the fields based on the provided totpfields + const filteredObjects = this.allFieldData.filter((obj) => + totpFieldArray.some((o) => o.opid === obj.opid), + ); + + // Return null if no matching objects are found + if (filteredObjects.length === 0) { + return null; + } + // Calculate the smallest left and largest right values to determine width + const left = Math.min( + ...filteredObjects.filter((obj) => rectHasSize(obj.rect)).map((obj) => obj.rect.left), + ); + const largestRight = Math.max( + ...filteredObjects.filter((obj) => rectHasSize(obj.rect)).map((obj) => obj.rect.right), + ); + + const width = largestRight - left; + + return { left, width }; + } + + /** + * calculates the postion for multi-input totp field inline button + * @param totpFieldArray - the totp fields used to evaluate the position of the menu + */ + private calculateTotpMultiInputButtonBounds(totpFieldArray: AutofillField[]) { + const filteredObjects = this.allFieldData.filter((obj) => + totpFieldArray.some((o) => o.opid === obj.opid), + ); + + if (filteredObjects.length === 0) { + return null; + } + + const maxRight = Math.max(...filteredObjects.map((obj) => obj.rect.right)); + const maxObject = filteredObjects.find((obj) => obj.rect.right === maxRight); + const top = maxObject.rect.top - maxObject.rect.height * 0.39; + const left = maxRight - maxObject.rect.height * 0.3; + + return { left, top }; + } + /** * Updates the position of either the inline menu list or button. The position * is based on the focused field's position and dimensions. @@ -1472,8 +1539,17 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameTopOffset = subFrameOffsets?.top || 0; const subFrameLeftOffset = subFrameOffsets?.left || 0; - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; + const { width, height } = this.focusedFieldData.focusedFieldRects; + let { top, left } = this.focusedFieldData.focusedFieldRects; const { paddingRight, paddingLeft } = this.focusedFieldData.focusedFieldStyles; + + if (this.isTotpFieldForCurrentField()) { + const totpFields = this.getTotpFields(); + if (totpFields.length > 1) { + ({ left, top } = this.calculateTotpMultiInputButtonBounds(totpFields)); + } + } + let elementOffset = height * 0.37; if (height >= 35) { elementOffset = height >= 50 ? height * 0.47 : height * 0.42; @@ -1512,7 +1588,16 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameTopOffset = subFrameOffsets?.top || 0; const subFrameLeftOffset = subFrameOffsets?.left || 0; - const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; + const { top, height } = this.focusedFieldData.focusedFieldRects; + let { left, width } = this.focusedFieldData.focusedFieldRects; + + if (this.isTotpFieldForCurrentField()) { + const totpFields = this.getTotpFields(); + + if (totpFields.length > 1) { + ({ left, width } = this.calculateTotpMultiInputMenuBounds(totpFields)); + } + } this.inlineMenuPosition.list = { top: Math.round(top + height + subFrameTopOffset), @@ -1535,7 +1620,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param sender - The sender of the extension message */ private setFocusedFieldData( - { focusedFieldData }: OverlayBackgroundExtensionMessage, + { focusedFieldData, allFieldsRect }: OverlayBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, ) { if ( @@ -1552,6 +1637,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const previousFocusedFieldData = this.focusedFieldData; this.focusedFieldData = { ...focusedFieldData, tabId: sender.tab.id, frameId: sender.frameId }; + this.allFieldData = allFieldsRect; this.isFieldCurrentlyFocused = true; if (this.shouldUpdatePasswordGeneratorMenuOnFieldFocus()) { diff --git a/apps/browser/src/autofill/content/auto-submit-login.spec.ts b/apps/browser/src/autofill/content/auto-submit-login.spec.ts index ff1dbd4e945..d70fc1e7446 100644 --- a/apps/browser/src/autofill/content/auto-submit-login.spec.ts +++ b/apps/browser/src/autofill/content/auto-submit-login.spec.ts @@ -46,6 +46,8 @@ describe("AutoSubmitLogin content script", () => { beforeEach(() => { jest.useFakeTimers(); setupEnvironmentDefaults(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./auto-submit-login"); }); diff --git a/apps/browser/src/autofill/content/components/buttons/edit-button.ts b/apps/browser/src/autofill/content/components/buttons/edit-button.ts index 695cbfd3b9d..cacd2b59f0e 100644 --- a/apps/browser/src/autofill/content/components/buttons/edit-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts @@ -23,6 +23,8 @@ export function EditButton({ title=${buttonText} class=${editButtonStyles({ disabled, theme })} @click=${(event: Event) => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions !disabled && buttonAction(event); }} > diff --git a/apps/browser/src/autofill/content/components/cipher/types.ts b/apps/browser/src/autofill/content/components/cipher/types.ts index 24f528c5246..acdee756570 100644 --- a/apps/browser/src/autofill/content/components/cipher/types.ts +++ b/apps/browser/src/autofill/content/components/cipher/types.ts @@ -1,3 +1,5 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const CipherTypes = { Login: 1, SecureNote: 2, @@ -7,6 +9,8 @@ const CipherTypes = { type CipherType = (typeof CipherTypes)[keyof typeof CipherTypes]; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const CipherRepromptTypes = { None: 0, Password: 1, diff --git a/apps/browser/src/autofill/content/content-message-handler.spec.ts b/apps/browser/src/autofill/content/content-message-handler.spec.ts index 226fcb4bd61..a37a2e07678 100644 --- a/apps/browser/src/autofill/content/content-message-handler.spec.ts +++ b/apps/browser/src/autofill/content/content-message-handler.spec.ts @@ -19,6 +19,8 @@ describe("ContentMessageHandler", () => { ); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./content-message-handler"); }); diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts index b260bfef632..d3e9c29ab58 100644 --- a/apps/browser/src/autofill/content/notification-bar.ts +++ b/apps/browser/src/autofill/content/notification-bar.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ServerConfig } from "../../../../../libs/common/src/platform/abstractions/config/server-config"; +import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; + import { AddLoginMessageData, ChangePasswordMessageData, diff --git a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts index 1ad985bc8e9..317f63e756c 100644 --- a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts +++ b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts @@ -6,6 +6,8 @@ describe("TriggerAutofillScriptInjection", () => { describe("init", () => { it("sends a message to the extension background", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../content/trigger-autofill-script-injection"); expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts index fde98a58a5f..fd6a79733cb 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayButton from "./autofill-overlay-button.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-button.scss"); (function () { diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts index 714ccbfbee5..5d587bd4293 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayList from "./autofill-overlay-list.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-list.scss"); (function () { diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts index 94bef354a79..8885ed6299c 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts @@ -60,6 +60,8 @@ describe("Fido2 Content Script", () => { chrome.runtime.connect = jest.fn(() => portSpy); it("destroys the messenger when the port is disconnected", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); triggerPortOnDisconnectEvent(portSpy); @@ -75,6 +77,8 @@ describe("Fido2 Content Script", () => { const mockResult = { credentialId: "mock" } as CreateCredentialResult; jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue(mockResult); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const response = await messenger.handler!(message, new AbortController()); @@ -99,6 +103,8 @@ describe("Fido2 Content Script", () => { data: mock(), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, new AbortController()); @@ -121,6 +127,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); const abortSpy = jest.spyOn(abortController.signal, "removeEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -141,6 +149,8 @@ describe("Fido2 Content Script", () => { abortController.abort(); }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -161,6 +171,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue({ error: errorMessage }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const result = messenger.handler!(message, abortController); @@ -175,6 +187,8 @@ describe("Fido2 Content Script", () => { contentType: "application/json", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); @@ -193,6 +207,8 @@ describe("Fido2 Content Script", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts index 6b9b41b5aac..69e17d26fe5 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts @@ -22,6 +22,8 @@ describe("FIDO2 page-script for manifest v2", () => { it("skips appending the `page-script.js` file if the document contentType is not `text/html`", () => { Object.defineProperty(window.document, "contentType", { value: "text/plain", writable: true }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).not.toHaveBeenCalled(); @@ -33,6 +35,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); @@ -48,6 +52,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index fd033090cd4..4c1761c37ba 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -267,6 +267,8 @@ import { Messenger } from "./messaging/messenger"; clearWaitForFocus(); void messenger.destroy(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { /** empty */ } diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index 31e8c941e86..f1aec69193b 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -55,6 +55,8 @@ describe("Fido2 page script with native WebAuthn support", () => { setupMockedWebAuthnSupport(); beforeAll(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); }); @@ -166,6 +168,8 @@ describe("Fido2 page script with native WebAuthn support", () => { contentType: "json/application", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); @@ -184,6 +188,8 @@ describe("Fido2 page script with native WebAuthn support", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts index e354453ca59..af1838ec942 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts @@ -50,6 +50,8 @@ describe("Fido2 page script without native WebAuthn support", () => { const mockCreateCredentialsResult = createCreateCredentialResultMock(); const mockCredentialRequestOptions = createCredentialRequestOptionsMock(); const mockCredentialAssertResult = createAssertCredentialResultMock(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); afterEach(() => { diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index 7660b4ce5f0..c0be60f1cd0 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -1,3 +1,4 @@ +import { FieldRect } from "../background/abstractions/overlay.background"; // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { AutofillFieldQualifierType } from "../enums/autofill-field.enums"; @@ -124,4 +125,9 @@ export default class AutofillField { fieldQualifier?: AutofillFieldQualifierType; accountCreationFieldType?: InlineMenuAccountCreationFieldTypes; + + /** + * used for totp multiline calculations + */ + fieldRect?: FieldRect; } diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index a4b8ae44b6a..2c0ebe8e8e7 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -15,6 +15,8 @@ import { NotificationBarIframeInitData, } from "./abstractions/notification-bar"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./bar.scss"); const logService = new ConsoleLogService(false); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts index 0ed14a520c1..36ef3897c56 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuButton } from "./autofill-inline-menu-button"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./button.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts index c302c50b4a4..b46b208b084 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuList } from "./autofill-inline-menu-list"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./list.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts index 16d5c29d574..522b968e533 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts @@ -1,3 +1,5 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./menu-container.scss"); import { AutofillInlineMenuContainer } from "./autofill-inline-menu-container"; diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html deleted file mode 100644 index 852fd4a0e81..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html +++ /dev/null @@ -1,36 +0,0 @@ -
-
- -
-
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts deleted file mode 100644 index 2101979fe36..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from "@angular/core"; - -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; - -@Component({ - selector: "app-fido2-cipher-row-v1", - templateUrl: "fido2-cipher-row-v1.component.html", - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class Fido2CipherRowV1Component { - @Output() onSelected = new EventEmitter(); - @Input() cipher: CipherView; - @Input() last: boolean; - @Input() title: string; - @Input() isSearching: boolean; - @Input() isSelected: boolean; - - protected selectCipher(c: CipherView) { - this.onSelected.emit(c); - } - - /** - * Returns a subname for the cipher. - * If this has a FIDO2 credential, and the cipher.name is different from the FIDO2 credential's rpId, return the rpId. - * @param c Cipher - * @returns - */ - protected getSubName(c: CipherView): string | null { - const fido2Credentials = c.login?.fido2Credentials; - - if (!fido2Credentials || fido2Credentials.length === 0) { - return null; - } - - const [fido2Credential] = fido2Credentials; - - return c.name !== fido2Credential.rpId ? fido2Credential.rpId : null; - } -} diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html deleted file mode 100644 index 9f6c0aca50d..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - -
- -
-
- -
-
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts deleted file mode 100644 index b7a1bc195ad..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts +++ /dev/null @@ -1,115 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { ConnectedPosition } from "@angular/cdk/overlay"; -import { Component } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -import { fido2PopoutSessionData$ } from "../../../vault/popup/utils/fido2-popout-session-data"; -import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-fido2-user-interface.service"; - -@Component({ - selector: "app-fido2-use-browser-link-v1", - templateUrl: "fido2-use-browser-link-v1.component.html", - animations: [ - trigger("transformPanel", [ - state( - "void", - style({ - opacity: 0, - }), - ), - transition( - "void => open", - animate( - "100ms linear", - style({ - opacity: 1, - }), - ), - ), - transition("* => void", animate("100ms linear", style({ opacity: 0 }))), - ]), - ], -}) -export class Fido2UseBrowserLinkV1Component { - showOverlay = false; - isOpen = false; - overlayPosition: ConnectedPosition[] = [ - { - originX: "start", - originY: "bottom", - overlayX: "start", - overlayY: "top", - offsetY: 5, - }, - ]; - - protected fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - private domainSettingsService: DomainSettingsService, - private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - ) {} - - toggle() { - this.isOpen = !this.isOpen; - } - - close() { - this.isOpen = false; - } - - /** - * Aborts the current FIDO2 session and fallsback to the browser. - * @param excludeDomain - Identifies if the domain should be excluded from future FIDO2 prompts. - */ - protected async abort(excludeDomain = true) { - this.close(); - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - - if (!excludeDomain) { - this.abortSession(sessionData.sessionId); - return; - } - // Show overlay to prevent the user from interacting with the page. - this.showOverlay = true; - await this.handleDomainExclusion(sessionData.senderUrl); - // Give the user a chance to see the toast before closing the popout. - await Utils.delay(2000); - this.abortSession(sessionData.sessionId); - } - - /** - * Excludes the domain from future FIDO2 prompts. - * @param uri - The domain uri to exclude from future FIDO2 prompts. - */ - private async handleDomainExclusion(uri: string) { - const existingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); - - const validDomain = Utils.getHostname(uri); - const savedDomains: NeverDomains = { - ...existingDomains, - }; - savedDomains[validDomain] = null; - - await this.domainSettingsService.setNeverDomains(savedDomains); - - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("domainAddedToExcludedDomains", validDomain), - ); - } - - private abortSession(sessionId: string) { - BrowserFido2UserInterfaceSession.abortPopout(sessionId, true); - } -} diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html deleted file mode 100644 index 8a052fbc5b7..00000000000 --- a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html +++ /dev/null @@ -1,142 +0,0 @@ - -
-
-
- - - - - - -
- - -
- -
-
-
- - - -
-

- {{ subtitleText | i18n }} -

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

{{ "passkeyAlreadyExists" | i18n }}

-
-
- -
-
- -
-
- -
-

{{ "noPasskeysFoundForThisApplication" | i18n }}

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

- {{ "autofill" | i18n }} -

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

{{ "additionalOptions" | i18n }}

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

- {{ "excludedDomains" | i18n }} -

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

- {{ "notifications" | i18n }} -

-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
- -
-
-
diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts deleted file mode 100644 index 442e91e55eb..00000000000 --- a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; - -import { enableAccountSwitching } from "../../../platform/flags"; - -@Component({ - selector: "autofill-notification-v1-settings", - templateUrl: "notifications-v1.component.html", -}) -export class NotificationsSettingsV1Component implements OnInit { - enableAddLoginNotification = false; - enableChangedPasswordNotification = false; - enablePasskeys = true; - accountSwitcherEnabled = false; - - constructor( - private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, - private vaultSettingsService: VaultSettingsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - this.enableAddLoginNotification = await firstValueFrom( - this.userNotificationSettingsService.enableAddedLoginPrompt$, - ); - - this.enableChangedPasswordNotification = await firstValueFrom( - this.userNotificationSettingsService.enableChangedPasswordPrompt$, - ); - - this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); - } - - async updateAddLoginNotification() { - await this.userNotificationSettingsService.setEnableAddedLoginPrompt( - this.enableAddLoginNotification, - ); - } - - async updateChangedPasswordNotification() { - await this.userNotificationSettingsService.setEnableChangedPasswordPrompt( - this.enableChangedPasswordNotification, - ); - } - - async updateEnablePasskeys() { - await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys); - } -} diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index daa65d74ae6..acf0dedde27 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -957,8 +957,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ accountCreationFieldType: autofillFieldData?.accountCreationFieldType, }; + const allFields = this.formFieldElements; + const allFieldsRect = []; + + for (const key of allFields.keys()) { + const rect = await this.getMostRecentlyFocusedFieldRects(key); + allFieldsRect.push({ ...allFields.get(key), rect }); // Add the combined result to the array + } + await this.sendExtensionMessage("updateFocusedFieldData", { focusedFieldData: this.focusedFieldData, + allFieldsRect, }); } @@ -1408,6 +1417,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ url.origin + pathWithoutTrailingSlashAndSearch, url.origin + pathWithoutTrailingSlashSearchAndHash, ]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_error) { return null; } diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index 0dbc246b235..16310397a03 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -235,6 +235,8 @@ export class DomQueryService implements DomQueryServiceInterface { if ((chrome as any).dom?.openOrClosedShadowRoot) { try { return (chrome as any).dom.openOrClosedShadowRoot(node); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/apps/browser/src/autofill/types/index.ts b/apps/browser/src/autofill/types/index.ts index a14ef1330cc..58ac95e7edf 100644 --- a/apps/browser/src/autofill/types/index.ts +++ b/apps/browser/src/autofill/types/index.ts @@ -1,5 +1,5 @@ +import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; -import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum"; import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; import { CipherType } from "@bitwarden/common/vault/enums"; diff --git a/apps/browser/src/autofill/utils/index.ts b/apps/browser/src/autofill/utils/index.ts index 12d26914d82..0e102dcfd99 100644 --- a/apps/browser/src/autofill/utils/index.ts +++ b/apps/browser/src/autofill/utils/index.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { FieldRect } from "../background/abstractions/overlay.background"; import { AutofillPort } from "../enums/autofill-port.enum"; import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types"; @@ -545,6 +546,17 @@ export const specialCharacterToKeyMap: Record = { "/": "forwardSlashCharacterDescriptor", }; +/** + * Determines if the current rect values are not all 0. + */ +export function rectHasSize(rect: FieldRect): boolean { + if (rect.right > 0 && rect.left > 0 && rect.top > 0 && rect.bottom > 0) { + return true; + } + + return false; +} + /** * Checks if all the values corresponding to the specified keys in an object are null. * If no keys are specified, checks all keys in the object. diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 4bec3d6cc0a..98f3867e5ff 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -632,11 +632,6 @@ export default class MainBackground { this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); - this.biometricsService = new BackgroundBrowserBiometricsService( - runtimeNativeMessagingBackground, - this.logService, - ); - this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); this.pinService = new PinService( @@ -665,6 +660,13 @@ export default class MainBackground { this.kdfConfigService, ); + this.biometricsService = new BackgroundBrowserBiometricsService( + runtimeNativeMessagingBackground, + this.logService, + this.keyService, + this.biometricStateService, + ); + this.appIdService = new AppIdService(this.storageService, this.logService); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 116d048d2e8..b08f1c8b566 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -257,7 +257,7 @@ export class NativeMessagingBackground { message.command == BiometricsCommands.Unlock || message.command == BiometricsCommands.IsAvailable ) { - // TODO remove after 2025.01 + // TODO remove after 2025.3 // wait until there is no other callbacks, or timeout const call = await firstValueFrom( race( @@ -342,6 +342,8 @@ export class NativeMessagingBackground { }; } this.port.postMessage(msg); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info( "[Native Messaging IPC] Disconnected from Bitwarden Desktop app because of the native port disconnecting.", diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 863ca26b36e..38bb2ec50c9 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -259,9 +259,7 @@ export default class RuntimeBackground { await this.main.refreshBadge(); await this.main.refreshMenu(false); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); break; } case "addToLockedVaultPendingNotifications": @@ -288,9 +286,7 @@ export default class RuntimeBackground { await this.configService.ensureConfigFetched(); await this.main.updateOverlayCiphers(); - if (await this.configService.getFeatureFlag(FeatureFlag.ExtensionRefresh)) { - await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); - } + await this.autofillService.setAutoFillOnPageLoadOrgPolicy(); } break; case "openPopup": diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 8e6fc562d14..e943f241f77 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -1,9 +1,17 @@ import { Injectable } from "@angular/core"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; -import { BiometricsService, BiometricsCommands, BiometricsStatus } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsCommands, + BiometricsStatus, + KeyService, + BiometricStateService, +} from "@bitwarden/key-management"; import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; import { BrowserApi } from "../../platform/browser/browser-api"; @@ -13,6 +21,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { constructor( private nativeMessagingBackground: () => NativeMessagingBackground, private logService: LogService, + private keyService: KeyService, + private biometricStateService: BiometricStateService, ) { super(); } @@ -65,6 +75,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { } } return BiometricsStatus.Available; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } @@ -74,12 +86,20 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); + // todo remove after 2025.3 if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { const response = await this.nativeMessagingBackground().callCommand({ command: BiometricsCommands.Unlock, }); if (response.response == "unlocked") { - return response.userKeyB64; + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.biometricStateService.setFingerprintValidated(true); + await this.keyService.setUserKey(userKey, userId); + return userKey; + } } else { return null; } @@ -89,7 +109,15 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: userId, }); if (response.response) { - return response.userKeyB64; + // In case the requesting foreground context dies (popup), the userkey should still be set, so the user is unlocked / the setting should be enabled + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.biometricStateService.setFingerprintValidated(true); + await this.keyService.setUserKey(userKey, userId); + return userKey; + } } else { return null; } @@ -98,6 +126,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { this.logService.info("Biometric unlock for user failed", e); throw new Error("Biometric unlock failed"); } + + return null; } async getBiometricsStatusForUser(id: UserId): Promise { @@ -114,6 +144,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: id, }) ).response; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index 0235ad5bd9c..d248a630cc6 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -29,13 +29,13 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { async unlockWithBiometricsForUser(userId: UserId): Promise { const response = await BrowserApi.sendMessageWithResponse<{ - result: string; + result: UserKey; error: string; }>(BiometricsCommands.UnlockWithBiometricsForUser, { userId }); if (!response.result) { return null; } - return SymmetricCryptoKey.fromString(response.result) as UserKey; + return SymmetricCryptoKey.fromString(response.result.keyB64) as UserKey; } async getBiometricsStatusForUser(id: UserId): Promise { diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts index 4b0323d5ebe..92830c35aca 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts @@ -9,7 +9,12 @@ import { import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { KeyService, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { + KeyService, + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; import { UnlockOptions } from "@bitwarden/key-management/angular"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; @@ -26,6 +31,7 @@ describe("ExtensionLockComponentService", () => { let vaultTimeoutSettingsService: MockProxy; let keyService: MockProxy; let routerService: MockProxy; + let biometricStateService: MockProxy; beforeEach(() => { userDecryptionOptionsService = mock(); @@ -35,6 +41,7 @@ describe("ExtensionLockComponentService", () => { vaultTimeoutSettingsService = mock(); keyService = mock(); routerService = mock(); + biometricStateService = mock(); TestBed.configureTestingModule({ providers: [ @@ -67,6 +74,10 @@ describe("ExtensionLockComponentService", () => { provide: BrowserRouterService, useValue: routerService, }, + { + provide: BiometricStateService, + useValue: biometricStateService, + }, ], }); @@ -306,6 +317,7 @@ describe("ExtensionLockComponentService", () => { platformUtilsService.supportsSecureStorage.mockReturnValue( mockInputs.platformSupportsSecureStorage, ); + biometricStateService.biometricUnlockEnabled$ = of(true); // PIN pinService.isPinDecryptionAvailable.mockResolvedValue(mockInputs.pinDecryptionAvailable); diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts index f21beb91cff..711ae4e378f 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -1,14 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { inject } from "@angular/core"; -import { combineLatest, defer, map, Observable } from "rxjs"; +import { combineLatest, defer, firstValueFrom, map, Observable } from "rxjs"; import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { UserId } from "@bitwarden/common/types/guid"; -import { BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; +import { + BiometricsService, + BiometricsStatus, + BiometricStateService, +} from "@bitwarden/key-management"; import { LockComponentService, UnlockOptions } from "@bitwarden/key-management/angular"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; @@ -19,6 +23,7 @@ export class ExtensionLockComponentService implements LockComponentService { private readonly biometricsService = inject(BiometricsService); private readonly pinService = inject(PinServiceAbstraction); private readonly routerService = inject(BrowserRouterService); + private readonly biometricStateService = inject(BiometricStateService); getPreviousUrl(): string | null { return this.routerService.getPreviousUrl(); @@ -45,7 +50,21 @@ export class ExtensionLockComponentService implements LockComponentService { getAvailableUnlockOptions$(userId: UserId): Observable { return combineLatest([ // Note: defer is preferable b/c it delays the execution of the function until the observable is subscribed to - defer(async () => await this.biometricsService.getBiometricsStatusForUser(userId)), + defer(async () => { + if (!(await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$))) { + return BiometricsStatus.NotEnabledLocally; + } else { + // TODO remove after 2025.3 + // remove after backward compatibility code for old biometrics ipc protocol is removed + const result: BiometricsStatus = (await Promise.race([ + this.biometricsService.getBiometricsStatusForUser(userId), + new Promise((resolve) => + setTimeout(() => resolve(BiometricsStatus.DesktopDisconnected), 1000), + ), + ])) as BiometricsStatus; + return result; + } + }), this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), defer(() => this.pinService.isPinDecryptionAvailable(userId)), ]).pipe( diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 9e4eb78291a..86ea0eebbc8 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.1.0", + "version": "2025.1.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index ef7ede6b056..ae7c888eb9b 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.1.0", + "version": "2025.1.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/platform/flags.ts b/apps/browser/src/platform/flags.ts index 383e982f065..2b1040bcd8a 100644 --- a/apps/browser/src/platform/flags.ts +++ b/apps/browser/src/platform/flags.ts @@ -11,13 +11,11 @@ import { GroupPolicyEnvironment } from "../admin-console/types/group-policy-envi import { BrowserApi } from "./browser/browser-api"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = { accountSwitching?: boolean; } & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = { managedEnvironment?: GroupPolicyEnvironment; } & SharedDevFlags; diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts index 4065f2e46d7..67fa920d18d 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts @@ -8,6 +8,8 @@ describe("OffscreenDocument", () => { const browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); const consoleErrorSpy = jest.spyOn(console, "error"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../offscreen-document/offscreen-document"); describe("init", () => { diff --git a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts index 0919de46776..b177497305b 100644 --- a/apps/browser/src/platform/services/browser-script-injector.service.spec.ts +++ b/apps/browser/src/platform/services/browser-script-injector.service.spec.ts @@ -63,7 +63,7 @@ describe("ScriptInjectorService", () => { configService.getFeatureFlag$.mockImplementation(() => of(false)); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, configService); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); - domainSettingsService.blockedInteractionsUris$ = of(null); + domainSettingsService.blockedInteractionsUris$ = of({}); scriptInjectorService = new BrowserScriptInjectorService( domainSettingsService, platformUtilsService, diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts index 949ecebde8a..4d6a403a18a 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts @@ -48,6 +48,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( encrypted, sessionKey, @@ -69,6 +71,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( encrypted, sessionKey, diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.ts index bdc0bed80c0..900212ddefa 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.ts @@ -198,6 +198,8 @@ export class LocalBackedSessionStorageService private compareValues(value1: T, value2: T): boolean { try { return compareValues(value1, value2); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.error( `error comparing values\n${JSON.stringify(value1)}\n${JSON.stringify(value2)}`, diff --git a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts index a2f0c78cd52..0499f34a4ae 100644 --- a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts +++ b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts @@ -17,6 +17,8 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 8e48104737a..d55bebfa0c3 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -17,7 +17,6 @@ import { unauthGuardFn, } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap"; import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { @@ -71,14 +70,10 @@ import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component" import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; -import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; -import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-domains.component"; -import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; -import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; @@ -148,11 +143,12 @@ const routes: Routes = [ canActivate: [unauthGuardFn(unauthRouteOverrides), unauthUiRefreshRedirect("/login")], data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(Fido2V1Component, Fido2Component, { + { path: "fido2", + component: Fido2Component, canActivate: [fido2AuthGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, ...twofactorRefactorSwap( TwoFactorComponent, AnonLayoutWrapperComponent, @@ -321,22 +317,24 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(AutofillV1Component, AutofillComponent, { + { path: "autofill", + component: AutofillComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "account-security", component: AccountSecurityComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, { + { path: "notifications", + component: NotificationsSettingsComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "vault-settings", component: VaultSettingsV2Component, @@ -355,11 +353,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { + { path: "excluded-domains", + component: ExcludedDomainsComponent, canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, - }), + }, { path: "premium", component: PremiumV2Component, diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 8e51152be2e..9d4835889b9 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -21,6 +21,7 @@ import { ToastOptions, ToastService, } from "@bitwarden/components"; +import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service"; @@ -64,6 +65,8 @@ export class AppComponent implements OnInit, OnDestroy { private toastService: ToastService, private accountService: AccountService, private animationControlService: AnimationControlService, + private biometricStateService: BiometricStateService, + private biometricsService: BiometricsService, ) {} async ngOnInit() { @@ -100,7 +103,7 @@ export class AppComponent implements OnInit, OnDestroy { this.messageListener.allMessages$ .pipe( - tap((msg: any) => { + tap(async (msg: any) => { if (msg.command === "doneLoggingOut") { // TODO: PM-8544 - why do we call logout in the popup after receiving the doneLoggingOut message? Hasn't this already completeted logout? this.authService.logOut(async () => { @@ -117,6 +120,7 @@ export class AppComponent implements OnInit, OnDestroy { msg.command === "locked" && (msg.userId == null || msg.userId == this.activeUserId) ) { + await this.biometricsService.setShouldAutopromptNow(false); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.router.navigate(["lock"]); @@ -133,7 +137,8 @@ export class AppComponent implements OnInit, OnDestroy { this.toastService._showToast(msg); } else if (msg.command === "reloadProcess") { if (this.platformUtilsService.isSafari()) { - window.setTimeout(() => { + window.setTimeout(async () => { + await this.biometricStateService.updateLastProcessReload(); window.location.reload(); }, 2000); } diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 1fe8f1f18db..15a898aef53 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -35,17 +35,7 @@ import { SsoComponentV1 } from "../auth/popup/sso-v1.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; -import { Fido2CipherRowV1Component } from "../autofill/popup/fido2/fido2-cipher-row-v1.component"; -import { Fido2CipherRowComponent } from "../autofill/popup/fido2/fido2-cipher-row.component"; -import { Fido2UseBrowserLinkV1Component } from "../autofill/popup/fido2/fido2-use-browser-link-v1.component"; -import { Fido2UseBrowserLinkComponent } from "../autofill/popup/fido2/fido2-use-browser-link.component"; -import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; -import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; -import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; -import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; -import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; -import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; import { HeaderComponent } from "../platform/popup/header.component"; @@ -87,10 +77,6 @@ import "../platform/popup/locales"; ScrollingModule, ServicesModule, DialogModule, - ExcludedDomainsComponent, - Fido2CipherRowComponent, - Fido2Component, - Fido2UseBrowserLinkComponent, FilePopoutCalloutComponent, AvatarModule, AccountComponent, @@ -112,15 +98,11 @@ import "../platform/popup/locales"; ColorPasswordPipe, ColorPasswordCountPipe, EnvironmentComponent, - ExcludedDomainsV1Component, - Fido2CipherRowV1Component, - Fido2UseBrowserLinkV1Component, HintComponent, HomeComponent, LoginViaAuthRequestComponentV1, LoginComponentV1, LoginDecryptionOptionsComponentV1, - NotificationsSettingsV1Component, RegisterComponent, SetPasswordComponent, SsoComponentV1, @@ -131,8 +113,6 @@ import "../platform/popup/locales"; UserVerificationComponent, VaultTimeoutInputComponent, RemovePasswordComponent, - Fido2V1Component, - AutofillV1Component, EnvironmentSelectorComponent, ], exports: [], diff --git a/apps/browser/src/popup/main.ts b/apps/browser/src/popup/main.ts index 8ffe0743bf6..dadd7917b99 100644 --- a/apps/browser/src/popup/main.ts +++ b/apps/browser/src/popup/main.ts @@ -4,7 +4,9 @@ import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/popup.scss"); +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/tailwind.css"); import { AppModule } from "./app.module"; diff --git a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts b/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts index 6270b3503b5..0d49595d2eb 100644 --- a/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts +++ b/apps/browser/src/services/vault-timeout/foreground-vault-timeout.service.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/src/abstractions/vault-timeout/vault-timeout.service"; -import { MessagingService } from "@bitwarden/common/src/platform/abstractions/messaging.service"; -import { UserId } from "@bitwarden/common/src/types/guid"; +import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { UserId } from "@bitwarden/common/types/guid"; export class ForegroundVaultTimeoutService implements BaseVaultTimeoutService { constructor(protected messagingService: MessagingService) {} diff --git a/apps/browser/src/tools/background/fileless-importer.background.spec.ts b/apps/browser/src/tools/background/fileless-importer.background.spec.ts index 429a0e12184..409fac9790f 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.spec.ts @@ -39,7 +39,7 @@ describe("FilelessImporterBackground ", () => { let tabMock: chrome.tabs.Tab; beforeEach(() => { - domainSettingsService.blockedInteractionsUris$ = of(null); + domainSettingsService.blockedInteractionsUris$ = of({}); policyService.policyAppliesToActiveUser$.mockImplementation(() => of(true)); scriptInjectorService = new BrowserScriptInjectorService( domainSettingsService, diff --git a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts index 432754ab91c..21fa44b8d3f 100644 --- a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts +++ b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts @@ -12,6 +12,8 @@ describe("LpFilelessImporter", () => { chrome.runtime.connect = jest.fn(() => portSpy); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-fileless-importer"); lpFilelessImporter = (globalThis as any).lpFilelessImporter; }); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts index 95b49ea00ec..8479235cc17 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts @@ -7,6 +7,8 @@ describe("LP Suppress Import Download for Manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts index bfff3787506..ff0ed381599 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts @@ -10,6 +10,8 @@ describe("LP Suppress Import Download", () => { jest.spyOn(Element.prototype, "appendChild"); jest.spyOn(window, "addEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download"); anchor = document.createElement("a"); diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html index 7c4ea3e5b46..047d168ecbb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html @@ -1,7 +1,7 @@ + {{ "autofillBlockedNoticeV2" | i18n }} + + {{ "autofillBlockedNoticeGuidance" | i18n }} + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts new file mode 100644 index 00000000000..3a17825f4fb --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts @@ -0,0 +1,53 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { Observable } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + BannerModule, + IconButtonModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; + +import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; + +const blockedURISettingsRoute = "/blocked-domains"; + +@Component({ + standalone: true, + imports: [ + BannerModule, + CommonModule, + IconButtonModule, + JslibModule, + LinkModule, + RouterModule, + TypographyModule, + ], + selector: "blocked-injection-banner", + templateUrl: "blocked-injection-banner.component.html", +}) +export class BlockedInjectionBanner implements OnInit { + /** + * Flag indicating that the banner should be shown + */ + protected showCurrentTabIsBlockedBanner$: Observable = + this.vaultPopupAutofillService.showCurrentTabIsBlockedBanner$; + + /** + * Hostname for current tab + */ + protected currentTabHostname?: string; + + blockedURISettingsRoute: string = blockedURISettingsRoute; + + constructor(private vaultPopupAutofillService: VaultPopupAutofillService) {} + + async ngOnInit() {} + + async handleCurrentTabIsBlockedBannerDismiss() { + await this.vaultPopupAutofillService.dismissCurrentTabIsBlockedBanner(); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts index be30b29fb18..d57b1d2fe36 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts @@ -50,7 +50,7 @@ export class NewItemDropdownV2Component implements OnInit { this.tab = await BrowserApi.getTabFromCurrentWindow(); } - async buildQueryParams(type: CipherType): Promise { + buildQueryParams(type: CipherType): AddEditQueryParams { const poppedOut = BrowserPopupUtils.inPopout(window); const loginDetails: { uri?: string; name?: string } = {}; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts index 38ec6056d19..1f67dd51c21 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts @@ -152,7 +152,7 @@ describe("VaultHeaderV2Component", () => { it("defaults the initial state to true", (done) => { // The initial value of the `state$` variable above is undefined component["initialDisclosureVisibility$"].subscribe((initialVisibility) => { - expect(initialVisibility).toBeTrue(); + expect(initialVisibility).toBe(true); done(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts index 8ec04045e6c..3b9dc9a1647 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts @@ -8,6 +8,8 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DisclosureTriggerForDirective, IconButtonModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DisclosureComponent } from "../../../../../../../../libs/components/src/disclosure/disclosure.component"; import { runInsideAngular } from "../../../../../platform/browser/run-inside-angular.operator"; import { VaultPopupListFiltersService } from "../../../../../vault/popup/services/vault-popup-list-filters.service"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index b5d92a386b3..c55e8d9fb26 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -1,23 +1,76 @@ -
- -

- {{ title }} -

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

+ {{ title }} +

+ + + + {{ ciphers.length }} + + + + + +
+
+ +
{{ description }}
+
+ + - + diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index a35c9cea1d5..c56d1c7d10d 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -23,9 +23,12 @@ import { import { CanDeleteCipherDirective, DecryptionFailureDialogComponent, + OrgIconDirective, PasswordRepromptService, } from "@bitwarden/vault"; +import { PopupCipherView } from "../../views/popup-cipher.view"; + @Component({ selector: "app-trash-list-items-container", templateUrl: "trash-list-items-container.component.html", @@ -39,6 +42,7 @@ import { CanDeleteCipherDirective, MenuModule, IconButtonModule, + OrgIconDirective, TypographyModule, DecryptionFailureDialogComponent, ], @@ -49,7 +53,7 @@ export class TrashListItemsContainerComponent { * The list of trashed items to display. */ @Input() - ciphers: CipherView[] = []; + ciphers: PopupCipherView[] = []; @Input() headerText: string; @@ -64,6 +68,17 @@ export class TrashListItemsContainerComponent { private router: Router, ) {} + /** + * The tooltip text for the organization icon for ciphers that belong to an organization. + */ + orgIconTooltip(cipher: PopupCipherView) { + if (cipher.collectionIds.length > 1) { + return this.i18nService.t("nCollections", cipher.collectionIds.length); + } + + return cipher.collections[0]?.name; + } + async restore(cipher: CipherView) { try { await this.cipherService.restoreWithServer(cipher.id); diff --git a/apps/browser/src/vault/popup/settings/trash.component.ts b/apps/browser/src/vault/popup/settings/trash.component.ts index 8bac22df53f..61843de31bc 100644 --- a/apps/browser/src/vault/popup/settings/trash.component.ts +++ b/apps/browser/src/vault/popup/settings/trash.component.ts @@ -8,7 +8,6 @@ import { VaultIcons } from "@bitwarden/vault"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; -import { VaultListItemsContainerComponent } from "../components/vault-v2/vault-list-items-container/vault-list-items-container.component"; import { VaultPopupItemsService } from "../services/vault-popup-items.service"; import { TrashListItemsContainerComponent } from "./trash-list-items-container/trash-list-items-container.component"; @@ -22,7 +21,6 @@ import { TrashListItemsContainerComponent } from "./trash-list-items-container/t PopupPageComponent, PopupHeaderComponent, PopOutComponent, - VaultListItemsContainerComponent, TrashListItemsContainerComponent, CalloutModule, NoItemsModule, diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index ea98321a499..0f73ccd6619 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -121,7 +121,7 @@ Bitwarden 密码管理器 - 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 + 无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 被 PCMag、WIRED、The Verge、CNET、G2 等评为最佳的密码管理器! @@ -135,11 +135,12 @@ 每个人都应该拥有的保持在线安全的工具 使用 Bitwarden 是免费的,没有广告,不会出售数据。Bitwarden 相信每个人都应该拥有保持在线安全的能力。高级计划提供了对高级功能的访问。 -使用 BITWARDEN 为您的团队提供支持 +使用 Bitwarden 为您的团队提供支持 团队计划和企业计划具有专业的商业功能。例如 SSO 集成、自托管、目录集成,以及 SCIM 配置、全局策略、API 访问、事件日志等。 使用 Bitwarden 保护您的劳动成果,并与同事共享敏感信息。 + 选择 Bitwarden 的更多理由: 世界级加密 @@ -164,10 +165,10 @@ Bitwarden 的翻译涵盖 60 多种语言,由全球社区使用 Crowdin 翻译 从任何浏览器、移动设备或桌面操作系统中安全地访问和共享 Bitwarden 密码库中的敏感数据。 Bitwarden 保护的不仅仅是密码 -Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息! +Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 进一步了解 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev! - 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 + 无论是在家中、工作中还是在旅途中,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 在多个设备间同步和访问您的密码库 diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index 2e8f9c9f817..d0ec8025c66 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -5,6 +5,7 @@ config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", + "../../libs/key-management/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", diff --git a/apps/cli/package.json b/apps/cli/package.json index 40f7933ef85..8a6dffb1cb3 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.0", + "version": "2025.1.1", "keywords": [ "bitwarden", "password", @@ -80,7 +80,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.69", + "tldts": "6.1.71", "zxcvbn": "4.4.2" } } diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 5b351efe459..e26d073326e 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -34,6 +34,8 @@ export class ShareCommand { if (req == null || req.length === 0) { return Response.badRequest("You must provide at least one collection id for this item."); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 2a3d5d85408..359ed08ca99 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -165,6 +165,8 @@ export class LoginCommand { if (options.method != null) { twoFactorMethod = parseInt(options.method, null); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } @@ -240,6 +242,8 @@ export class LoginCommand { if (twoFactorMethod != null) { try { selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index dd99d03b086..8efb414f5b2 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -57,6 +57,8 @@ export class EditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 454db2858ab..a1fec7a7472 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -152,11 +152,9 @@ export class GetCommand extends DownloadCommand { } } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect( + await this.eventCollectionService.collect( EventType.Cipher_ClientViewed, - id, + decCipher.id, true, decCipher.organizationId, ); diff --git a/apps/cli/src/platform/flags.ts b/apps/cli/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/cli/src/platform/flags.ts +++ b/apps/cli/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/cli/src/platform/services/node-env-secure-storage.service.ts b/apps/cli/src/platform/services/node-env-secure-storage.service.ts index 5e9fdd26b3c..2807509e428 100644 --- a/apps/cli/src/platform/services/node-env-secure-storage.service.ts +++ b/apps/cli/src/platform/services/node-env-secure-storage.service.ts @@ -87,6 +87,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } return Utils.fromBufferToB64(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Decrypt error."); return null; @@ -104,6 +106,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Session key is invalid."); } diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts index eff351be22a..a9264c50126 100644 --- a/apps/cli/src/tools/send/commands/create.command.ts +++ b/apps/cli/src/tools/send/commands/create.command.ts @@ -49,6 +49,8 @@ export class SendCreateCommand { if (req == null) { throw new Error("Null request"); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/tools/send/commands/edit.command.ts b/apps/cli/src/tools/send/commands/edit.command.ts index 11508d5c417..ed719b58311 100644 --- a/apps/cli/src/tools/send/commands/edit.command.ts +++ b/apps/cli/src/tools/send/commands/edit.command.ts @@ -41,6 +41,8 @@ export class SendEditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = SendResponse.fromJson(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index d27ba4f88ec..41a6681af55 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -47,6 +47,8 @@ export class SendReceiveCommand extends DownloadCommand { let urlObject: URL; try { urlObject = new URL(url); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Failed to parse the provided Send url"); } diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 13cd666754f..a28d070d19e 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -66,6 +66,8 @@ export class CreateCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/desktop/config/config.js b/apps/desktop/config/config.js index 404295dd0db..30a5c16bb2a 100644 --- a/apps/desktop/config/config.js +++ b/apps/desktop/config/config.js @@ -32,6 +32,8 @@ function log(configObj) { function loadConfig(configName) { try { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports return require(`./${configName}.json`); } catch (e) { if (e instanceof Error && e.code === "MODULE_NOT_FOUND") { diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 8affdca7768..fceef8e6e7a 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -916,7 +916,6 @@ dependencies = [ "pin-project", "pkcs8", "rand", - "retry", "rsa", "russh-cryptovec", "scopeguard", @@ -2388,15 +2387,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "retry" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" -dependencies = [ - "rand", -] - [[package]] name = "rsa" version = "0.9.6" diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index dde5a3b1c88..cc407f09ec2 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -6,18 +6,16 @@ version = "0.0.0" publish = false [features] -default = ["sys"] -manual_test = [] - -sys = [ +default = [ "dep:widestring", "dep:windows", "dep:core-foundation", "dep:security-framework", "dep:security-framework-sys", "dep:zbus", - "dep:zbus_polkit", + "dep:zbus_polkit" ] +manual_test = [] [dependencies] aes = "=0.8.4" @@ -36,7 +34,6 @@ futures = "=0.3.31" interprocess = { version = "=2.2.1", features = ["tokio"] } log = "=0.4.22" rand = "=0.8.5" -retry = "=2.0.0" russh-cryptovec = "=0.7.3" scopeguard = "=1.2.0" sha2 = "=0.10.8" diff --git a/apps/desktop/desktop_native/core/src/biometric/mod.rs b/apps/desktop/desktop_native/core/src/biometric/mod.rs index 7ad9bcb032e..79be43b1bfc 100644 --- a/apps/desktop/desktop_native/core/src/biometric/mod.rs +++ b/apps/desktop/desktop_native/core/src/biometric/mod.rs @@ -3,12 +3,16 @@ use anyhow::{anyhow, Result}; #[allow(clippy::module_inception)] #[cfg_attr(target_os = "linux", path = "unix.rs")] -#[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "macos.rs")] +#[cfg_attr(target_os = "windows", path = "windows.rs")] mod biometric; -use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; pub use biometric::Biometric; + +#[cfg(target_os = "windows")] +pub mod windows_focus; + +use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; use sha2::{Digest, Sha256}; use crate::crypto::{self, CipherString}; diff --git a/apps/desktop/desktop_native/core/src/biometric/windows.rs b/apps/desktop/desktop_native/core/src/biometric/windows.rs index c91b379c226..70813082faf 100644 --- a/apps/desktop/desktop_native/core/src/biometric/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric/windows.rs @@ -1,12 +1,15 @@ -use std::{ffi::c_void, str::FromStr}; +use std::{ + ffi::c_void, + str::FromStr, + sync::{atomic::AtomicBool, Arc}, +}; use anyhow::{anyhow, Result}; use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; use rand::RngCore; -use retry::delay::Fixed; use sha2::{Digest, Sha256}; use windows::{ - core::{factory, h, s, HSTRING}, + core::{factory, h, HSTRING}, Foundation::IAsyncOperation, Security::{ Credentials::{ @@ -14,17 +17,7 @@ use windows::{ }, Cryptography::CryptographicBuffer, }, - Win32::{ - Foundation::HWND, - System::WinRT::IUserConsentVerifierInterop, - UI::{ - Input::KeyboardAndMouse::{ - keybd_event, GetAsyncKeyState, SetFocus, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, - VK_MENU, - }, - WindowsAndMessaging::{FindWindowA, SetForegroundWindow}, - }, - }, + Win32::{Foundation::HWND, System::WinRT::IUserConsentVerifierInterop}, }; use crate::{ @@ -32,7 +25,10 @@ use crate::{ crypto::CipherString, }; -use super::{decrypt, encrypt}; +use super::{ + decrypt, encrypt, + windows_focus::{focus_security_prompt, set_focus}, +}; /// The Windows OS implementation of the biometric trait. pub struct Biometric {} @@ -103,8 +99,22 @@ impl super::BiometricTrait for Biometric { let challenge_buffer = CryptographicBuffer::CreateFromByteArray(&challenge)?; let async_operation = result.Credential()?.RequestSignAsync(&challenge_buffer)?; - focus_security_prompt()?; - let signature = async_operation.get()?; + focus_security_prompt(); + + let done = Arc::new(AtomicBool::new(false)); + let done_clone = done.clone(); + let _ = std::thread::spawn(move || loop { + if !done_clone.load(std::sync::atomic::Ordering::Relaxed) { + focus_security_prompt(); + std::thread::sleep(std::time::Duration::from_millis(500)); + } else { + break; + } + }); + + let signature = async_operation.get(); + done.store(true, std::sync::atomic::Ordering::Relaxed); + let signature = signature?; if signature.Status()? != KeyCredentialStatus::Success { return Err(anyhow!("Failed to sign data")); @@ -168,57 +178,6 @@ fn random_challenge() -> [u8; 16] { challenge } -/// Searches for a window that looks like a security prompt and set it as focused. -/// -/// Gives up after 1.5 seconds with a delay of 500ms between each try. -fn focus_security_prompt() -> Result<()> { - unsafe fn try_find_and_set_focus( - class_name: windows::core::PCSTR, - ) -> retry::OperationResult<(), ()> { - let hwnd = unsafe { FindWindowA(class_name, None) }; - if let Ok(hwnd) = hwnd { - set_focus(hwnd); - return retry::OperationResult::Ok(()); - } - retry::OperationResult::Retry(()) - } - - let class_name = s!("Credential Dialog Xaml Host"); - retry::retry_with_index(Fixed::from_millis(500), |current_try| { - if current_try > 3 { - return retry::OperationResult::Err(()); - } - - unsafe { try_find_and_set_focus(class_name) } - }) - .map_err(|_| anyhow!("Failed to find security prompt")) -} - -fn set_focus(window: HWND) { - let mut pressed = false; - - unsafe { - // Simulate holding down Alt key to bypass windows limitations - // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate#return-value - // The most significant bit indicates if the key is currently being pressed. This means the - // value will be negative if the key is pressed. - if GetAsyncKeyState(VK_MENU.0 as i32) >= 0 { - pressed = true; - keybd_event(VK_MENU.0 as u8, 0, KEYEVENTF_EXTENDEDKEY, 0); - } - let _ = SetForegroundWindow(window); - let _ = SetFocus(window); - if pressed { - keybd_event( - VK_MENU.0 as u8, - 0, - KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, - 0, - ); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs b/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs new file mode 100644 index 00000000000..d5e92a67de6 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/biometric/windows_focus.rs @@ -0,0 +1,28 @@ +use windows::{ + core::s, + Win32::{ + Foundation::HWND, + UI::{ + Input::KeyboardAndMouse::SetFocus, + WindowsAndMessaging::{FindWindowA, SetForegroundWindow}, + }, + }, +}; + +/// Searches for a window that looks like a security prompt and set it as focused. +/// Only works when the process has permission to foreground, either by being in foreground +/// Or by being given foreground permission https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow#remarks +pub fn focus_security_prompt() { + let class_name = s!("Credential Dialog Xaml Host"); + let hwnd = unsafe { FindWindowA(class_name, None) }; + if let Ok(hwnd) = hwnd { + set_focus(hwnd); + } +} + +pub(crate) fn set_focus(window: HWND) { + unsafe { + let _ = SetForegroundWindow(window); + let _ = SetFocus(window); + } +} diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index b63c773209f..4a6686cc1f5 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,16 +1,10 @@ pub mod autofill; -#[cfg(feature = "sys")] pub mod biometric; -#[cfg(feature = "sys")] pub mod clipboard; pub mod crypto; pub mod error; pub mod ipc; -#[cfg(feature = "sys")] pub mod password; -#[cfg(feature = "sys")] pub mod powermonitor; -#[cfg(feature = "sys")] pub mod process_isolation; -#[cfg(feature = "sys")] pub mod ssh_agent; diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index a0cfee8e1a0..acfd0dffb89 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -1,209 +1,132 @@ -const { existsSync, readFileSync } = require('fs') -const { join } = require('path') +const { existsSync } = require("fs"); +const { join } = require("path"); -const { platform, arch } = process +const { platform, arch } = process; -let nativeBinding = null -let localFileExisted = false -let loadError = null +let nativeBinding = null; +let localFileExisted = false; +let loadError = null; -function isMusl() { - // For Node 10 - if (!process.report || typeof process.report.getReport !== 'function') { - try { - return readFileSync('/usr/bin/ldd', 'utf8').includes('musl') - } catch (e) { - return true +function loadFirstAvailable(localFiles, nodeModule) { + for (const localFile of localFiles) { + if (existsSync(join(__dirname, localFile))) { + return require(`./${localFile}`); } - } else { - const { glibcVersionRuntime } = process.report.getReport().header - return !glibcVersionRuntime } + + require(nodeModule); } switch (platform) { - case 'android': + case "android": switch (arch) { - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'desktop_napi.android-arm64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.android-arm64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-android-arm64') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync(join(__dirname, 'desktop_napi.android-arm-eabi.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.android-arm-eabi.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-android-arm-eabi') - } - } catch (e) { - loadError = e - } - break + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.android-arm64.node"], + "@bitwarden/desktop-napi-android-arm64", + ); + break; + case "arm": + nativeBinding = loadFirstAvailable( + ["desktop_napi.android-arm.node"], + "@bitwarden/desktop-napi-android-arm", + ); + break; default: - throw new Error(`Unsupported architecture on Android ${arch}`) + throw new Error(`Unsupported architecture on Android ${arch}`); } - break - case 'win32': + break; + case "win32": switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.win32-x64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.win32-x64-msvc.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-win32-x64-msvc') - } - } catch (e) { - loadError = e - } - break - case 'ia32': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.win32-ia32-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.win32-ia32-msvc.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-win32-ia32-msvc') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.win32-arm64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.win32-arm64-msvc.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-win32-arm64-msvc') - } - } catch (e) { - loadError = e - } - break + case "x64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.win32-x64-msvc.node"], + "@bitwarden/desktop-napi-win32-x64-msvc", + ); + break; + case "ia32": + nativeBinding = loadFirstAvailable( + ["desktop_napi.win32-ia32-msvc.node"], + "@bitwarden/desktop-napi-win32-ia32-msvc", + ); + break; + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.win32-arm64-msvc.node"], + "@bitwarden/desktop-napi-win32-arm64-msvc", + ); + break; default: - throw new Error(`Unsupported architecture on Windows: ${arch}`) + throw new Error(`Unsupported architecture on Windows: ${arch}`); } - break - case 'darwin': + break; + case "darwin": switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'desktop_napi.darwin-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.darwin-x64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-darwin-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.darwin-arm64.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.darwin-arm64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-darwin-arm64') - } - } catch (e) { - loadError = e - } - break + case "x64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.darwin-x64.node"], + "@bitwarden/desktop-napi-darwin-x64", + ); + break; + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.darwin-arm64.node"], + "@bitwarden/desktop-napi-darwin-arm64", + ); + break; default: - throw new Error(`Unsupported architecture on macOS: ${arch}`) + throw new Error(`Unsupported architecture on macOS: ${arch}`); } - break - case 'freebsd': - if (arch !== 'x64') { - throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) - } - localFileExisted = existsSync(join(__dirname, 'desktop_napi.freebsd-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.freebsd-x64.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-freebsd-x64') - } - } catch (e) { - loadError = e - } - break - case 'linux': + break; + case "freebsd": + nativeBinding = loadFirstAvailable( + ["desktop_napi.freebsd-x64.node"], + "@bitwarden/desktop-napi-freebsd-x64", + ); + break; + case "linux": switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.linux-x64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.linux-x64-musl.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-linux-x64-musl') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.linux-arm64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./desktop_napi.linux-arm64-musl.node') - } else { - nativeBinding = require('@bitwarden/desktop-napi-linux-arm64-musl') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync( - join(__dirname, 'desktop_napi.linux-arm-gnueabihf.node') - ) + case "x64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.linux-x64-musl.node", "desktop_napi.linux-x64-gnu.node"], + "@bitwarden/desktop-napi-linux-x64-musl", + ); + break; + case "arm64": + nativeBinding = loadFirstAvailable( + ["desktop_napi.linux-arm64-musl.node", "desktop_napi.linux-arm64-gnu.node"], + "@bitwarden/desktop-napi-linux-arm64-musl", + ); + break; + case "arm": + nativeBinding = loadFirstAvailable( + ["desktop_napi.linux-arm-musl.node", "desktop_napi.linux-arm-gnu.node"], + "@bitwarden/desktop-napi-linux-arm-musl", + ); + localFileExisted = existsSync(join(__dirname, "desktop_napi.linux-arm-gnueabihf.node")); try { if (localFileExisted) { - nativeBinding = require('./desktop_napi.linux-arm-gnueabihf.node') + nativeBinding = require("./desktop_napi.linux-arm-gnueabihf.node"); } else { - nativeBinding = require('@bitwarden/desktop-napi-linux-arm-gnueabihf') + nativeBinding = require("@bitwarden/desktop-napi-linux-arm-gnueabihf"); } } catch (e) { - loadError = e + loadError = e; } - break + break; default: - throw new Error(`Unsupported architecture on Linux: ${arch}`) + throw new Error(`Unsupported architecture on Linux: ${arch}`); } - break + break; default: - throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`); } if (!nativeBinding) { if (loadError) { - throw loadError + throw loadError; } - throw new Error(`Failed to load native binding`) + throw new Error(`Failed to load native binding`); } -module.exports = nativeBinding +module.exports = nativeBinding; diff --git a/apps/desktop/desktop_native/proxy/Cargo.toml b/apps/desktop/desktop_native/proxy/Cargo.toml index 06e587b442d..3618a11a921 100644 --- a/apps/desktop/desktop_native/proxy/Cargo.toml +++ b/apps/desktop/desktop_native/proxy/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] anyhow = "=1.0.94" -desktop_core = { path = "../core", default-features = false } +desktop_core = { path = "../core" } futures = "=0.3.31" log = "=0.4.22" simplelog = "=0.12.2" diff --git a/apps/desktop/desktop_native/proxy/src/main.rs b/apps/desktop/desktop_native/proxy/src/main.rs index bfd84b206d7..ba29e00cf13 100644 --- a/apps/desktop/desktop_native/proxy/src/main.rs +++ b/apps/desktop/desktop_native/proxy/src/main.rs @@ -5,6 +5,9 @@ use futures::{FutureExt, SinkExt, StreamExt}; use log::*; use tokio_util::codec::LengthDelimitedCodec; +#[cfg(target_os = "windows")] +mod windows; + #[cfg(target_os = "macos")] embed_plist::embed_info_plist!("../../../resources/info.desktop_proxy.plist"); @@ -49,6 +52,9 @@ fn init_logging(log_path: &Path, console_level: LevelFilter, file_level: LevelFi /// #[tokio::main(flavor = "current_thread")] async fn main() { + #[cfg(target_os = "windows")] + let should_foreground = windows::allow_foreground(); + let sock_path = desktop_core::ipc::path("bitwarden"); let log_path = { @@ -142,6 +148,9 @@ async fn main() { // Listen to stdin and send messages to ipc processor. msg = stdin.next() => { + #[cfg(target_os = "windows")] + should_foreground.store(true, std::sync::atomic::Ordering::Relaxed); + match msg { Some(Ok(msg)) => { let m = String::from_utf8(msg.to_vec()).unwrap(); diff --git a/apps/desktop/desktop_native/proxy/src/windows.rs b/apps/desktop/desktop_native/proxy/src/windows.rs new file mode 100644 index 00000000000..cb0656fc7f8 --- /dev/null +++ b/apps/desktop/desktop_native/proxy/src/windows.rs @@ -0,0 +1,23 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +pub fn allow_foreground() -> Arc { + let should_foreground = Arc::new(AtomicBool::new(false)); + let should_foreground_clone = should_foreground.clone(); + let _ = std::thread::spawn(move || loop { + if !should_foreground_clone.load(Ordering::Relaxed) { + std::thread::sleep(std::time::Duration::from_millis(100)); + continue; + } + should_foreground_clone.store(false, Ordering::Relaxed); + + for _ in 0..60 { + desktop_core::biometric::windows_focus::focus_security_prompt(); + std::thread::sleep(std::time::Duration::from_millis(1000)); + } + }); + + should_foreground +} diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index c8114d947e4..4302f302473 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -20,7 +20,7 @@ "**/node_modules/@bitwarden/desktop-napi/index.js", "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node" ], - "electronVersion": "33.2.1", + "electronVersion": "33.3.1", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 4bf939c8b41..f727c903a7f 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -14,11 +14,11 @@ "module-alias": "2.2.3", "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -106,9 +106,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -421,9 +421,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 6ab267be4a8..b7da729d7d1 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -19,11 +19,11 @@ "module-alias": "2.2.3", "node-ipc": "9.2.1", "ts-node": "10.9.2", - "uuid": "11.0.3", + "uuid": "11.0.5", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts index 2f8e61c68a4..8d2d734677a 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.ts @@ -7,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts index 4f17bb5658a..93598bf9eef 100644 --- a/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts +++ b/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.ts @@ -7,6 +7,7 @@ import { hideBin } from "yargs/helpers"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; import { LogUtils } from "../log-utils"; import NativeMessageService from "../native-message.service"; diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index ee17e672a79..8513363956e 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -4,7 +4,9 @@ import { homedir } from "os"; import * as NodeIPC from "node-ipc"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import Deferred from "./deferred"; diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index cd84504c630..94fdde026b2 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -9,13 +9,21 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; +// eslint-disable-next-line no-restricted-imports import { DecryptedCommandData } from "../../src/models/native-messaging/decrypted-command-data"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessage } from "../../src/models/native-messaging/encrypted-message"; +// eslint-disable-next-line no-restricted-imports import { CredentialCreatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-create-payload"; +// eslint-disable-next-line no-restricted-imports import { CredentialUpdatePayload } from "../../src/models/native-messaging/encrypted-message-payloads/credential-update-payload"; +// eslint-disable-next-line no-restricted-imports import { EncryptedMessageResponse } from "../../src/models/native-messaging/encrypted-message-response"; +// eslint-disable-next-line no-restricted-imports import { MessageCommon } from "../../src/models/native-messaging/message-common"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessage } from "../../src/models/native-messaging/unencrypted-message"; +// eslint-disable-next-line no-restricted-imports import { UnencryptedMessageResponse } from "../../src/models/native-messaging/unencrypted-message-response"; import IPCService, { IPCOptions } from "./ipc.service"; diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c55f39d1b2a..8f6c6525a39 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.0", + "version": "2025.1.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/postcss.config.js b/apps/desktop/postcss.config.js index c4513687e89..c39e7ea0355 100644 --- a/apps/desktop/postcss.config.js +++ b/apps/desktop/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports, no-undef */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml index 234d37905cc..fea28052f8d 100644 --- a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml +++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml @@ -8,7 +8,6 @@ command: bitwarden.sh finish-args: - --share=ipc - --share=network - - --socket=wayland - --socket=x11 - --device=dri - --env=XDG_CURRENT_DESKTOP=Unity @@ -33,11 +32,14 @@ modules: - install bitwarden.sh /app/bin/bitwarden.sh sources: - type: dir + only-arches: [x86_64] path: ../dist/linux-unpacked + - type: dir + only-arches: [aarch64] + path: ../dist/linux-arm64-unpacked - type: script dest-filename: bitwarden.sh commands: - ulimit -c 0 - export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID" - - exec zypak-wrapper /app/bin/bitwarden-app --ozone-platform-hint=auto - --enable-features=WaylandWindowDecorations "$@" + - exec zypak-wrapper /app/bin/bitwarden-app "$@" diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index fd16cd5ffbe..30b17a44d12 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const child_process = require("child_process"); const path = require("path"); diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index dc60e9d1838..20c24c8a76b 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const path = require("path"); diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js index 4cb643c34d4..649fe3b6736 100644 --- a/apps/desktop/scripts/build-macos-extension.js +++ b/apps/desktop/scripts/build-macos-extension.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ const child = require("child_process"); const { exit } = require("process"); diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index c1a2fb3cffc..d2c984a6f24 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const concurrently = require("concurrently"); const rimraf = require("rimraf"); diff --git a/apps/desktop/sign.js b/apps/desktop/sign.js index 74c63932306..f0110ea195b 100644 --- a/apps/desktop/sign.js +++ b/apps/desktop/sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ exports.default = async function (configuration) { if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") { diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 19748e797bb..f3440975cf2 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -650,7 +650,7 @@ export class SettingsComponent implements OnInit, OnDestroy { const skipSupportedPlatformCheck = ipc.platform.allowBrowserintegrationOverride || ipc.platform.isDev; - if (skipSupportedPlatformCheck) { + if (!skipSupportedPlatformCheck) { if ( ipc.platform.deviceType === DeviceType.MacOsDesktop && !this.platformUtilsService.isMacAppStore() diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index e4ee5ec0473..e565681de93 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -67,6 +67,7 @@ import { SendComponent } from "./tools/send/send.component"; /** * Data properties acceptable for use in route objects in the desktop */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RouteDataProperties { // For any new route data properties, add them here. // then assert that the data object satisfies this interface in the route object. diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 5fefbf9ddff..a05b09e139e 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -330,6 +330,20 @@ export class AppComponent implements OnInit, OnDestroy { } break; } + case "upgradeOrganization": { + const upgradeConfirmed = await this.dialogService.openSimpleDialog({ + title: { key: "upgradeOrganization" }, + content: { key: "upgradeOrganizationDesc" }, + acceptButtonText: { key: "learnMore" }, + type: "info", + }); + if (upgradeConfirmed) { + this.platformUtilsService.launchUri( + "https://bitwarden.com/help/upgrade-from-individual-to-org/", + ); + } + break; + } case "emailVerificationRequired": { const emailVerificationConfirmed = await this.dialogService.openSimpleDialog({ title: { key: "emailVerificationRequired" }, diff --git a/apps/desktop/src/app/main.ts b/apps/desktop/src/app/main.ts index a0b490edaa6..287d66795d2 100644 --- a/apps/desktop/src/app/main.ts +++ b/apps/desktop/src/app/main.ts @@ -1,7 +1,9 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; +// eslint-disable-next-line @typescript-eslint/no-require-imports require("../scss/styles.scss"); +// eslint-disable-next-line @typescript-eslint/no-require-imports require("../scss/tailwind.css"); import { AppModule } from "./app.module"; diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.ts b/apps/desktop/src/auth/login/desktop-login-component.service.ts index 8a2daef49ff..dbf689e801c 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.ts @@ -65,6 +65,8 @@ export class DesktopLoginComponentService try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.toastService.showToast({ variant: "error", diff --git a/apps/desktop/src/auth/login/login-v1.component.ts b/apps/desktop/src/auth/login/login-v1.component.ts index 5b1a1c68d29..e0c3f794dba 100644 --- a/apps/desktop/src/auth/login/login-v1.component.ts +++ b/apps/desktop/src/auth/login/login-v1.component.ts @@ -249,6 +249,8 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.platformUtilsService.showToast( "error", diff --git a/apps/desktop/src/auth/two-factor-auth-duo.component.ts b/apps/desktop/src/auth/two-factor-auth-duo.component.ts index 72137dc5364..c238b753b64 100644 --- a/apps/desktop/src/auth/two-factor-auth-duo.component.ts +++ b/apps/desktop/src/auth/two-factor-auth-duo.component.ts @@ -21,6 +21,8 @@ import { TypographyModule, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component"; const BroadcasterSubscriptionId = "TwoFactorComponent"; diff --git a/apps/desktop/src/auth/two-factor-auth.component.ts b/apps/desktop/src/auth/two-factor-auth.component.ts index 29271b565c1..9e0898c39e2 100644 --- a/apps/desktop/src/auth/two-factor-auth.component.ts +++ b/apps/desktop/src/auth/two-factor-auth.component.ts @@ -4,19 +4,47 @@ import { Component } from "@angular/core"; import { ReactiveFormsModule } from "@angular/forms"; import { RouterLink } from "@angular/router"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthEmailComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-email.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthWebAuthnComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-webauthn.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthYubikeyComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-yubikey.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { JslibModule } from "../../../../libs/angular/src/jslib.module"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule } from "../../../../libs/components/src/async-actions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../libs/components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CheckboxModule } from "../../../../libs/components/src/checkbox"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FormFieldModule } from "../../../../libs/components/src/form-field"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "../../../../libs/components/src/link"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../libs/components/src/typography"; import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 1f569319953..5ee3ba7e029 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januarie" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Swak wagwoord geidentifiseer en gevind in 'n data lekkasie. Gebruik 'n sterk en unieke wagwoord om jou rekening te beskerm. Is jy seker dat jy hierdie wagwoord wil gebruik?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontroleer bekende data lekkasies vir hierdie wagwoord" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index a15dbb5f078..132021f7760 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -249,6 +249,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "يناير" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "كلمة مرور ضعيفة محددة وموجودة في خرق البيانات. استخدم كلمة مرور قوية وفريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة المرور هذه؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "تحقق من خروقات البيانات المعروفة لكلمة المرور هذه" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "لم يُعثر على أي منفذ مجاني لتسجيل الدخول." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "السماح" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 07519429f12..9af089d1ef9 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Yanvar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zəif parol məlumat pozuntusunda aşkarlandı və tapıldı. Hesabınızı qorumaq üçün güclü və unikal bir parol istifadə edin. Bu parolu istifadə etmək istədiyinizə əminsiniz?" }, + "useThisPassword": { + "message": "Bu parolu istifadə et" + }, + "useThisUsername": { + "message": "Bu istifadəçi adını istifadə et" + }, "checkForBreaches": { "message": "Bu parol üçün bilinən məlumat pozuntularını yoxlayın" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "SSO giriş üçün açıq port tapıla bilmədi." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrik kilid açma indi əlçatmazdır." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Yanlış konfiqurasiya edilmiş sistem fayllarına görə biometrik kilid açma əlçatmazdır." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Bitwarden masaüstü tətbiqində $EMAIL$ üçün fəal olmadığına görə biometrik kilid açma əlçatmazdır.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." + }, "authorize": { "message": "Səlahiyyət ver" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e89d6b5fa6f..e778c59525f 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Студзень" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Вызначаны ненадзейны пароль, які знойдзены ва ўцечках даных. Выкарыстоўвайце надзейныя і ўнікальныя паролі для абароны свайго ўліковага запісу. Вы сапраўды хочаце выкарыстоўваць гэты пароль?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Праверыць у вядомых уцечках даных для гэтага пароля" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 9770e24c5ad..9b000177bbe 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "януари" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Разпозната е слаба парола. Използвайте силна парола, за да защитете данните си. Наистина ли искате да използвате слаба парола?" }, + "useThisPassword": { + "message": "Използване на тази парола" + }, + "useThisUsername": { + "message": "Използване на това потребителско име" + }, "checkForBreaches": { "message": "Проверяване в известните случаи на изтекли данни за тази парола" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Не могат да бъдат открити свободни портове за еднократната идентификация." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Отключването с биометрични данни не е налично в момента." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Отключването с биометрични данни не е налично поради неправилно настроени системни файлове." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Отключването с биометрични данни не е налично, тъй като не е включено за $EMAIL$ в приложението на Битуорден за компютър.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Отключването с биометрични данни не е налично по неизвестна причина." + }, "authorize": { "message": "Упълномощаване" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 8fe871a956f..069c58d751d 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ত্রুটি" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "জানুয়ারী" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index e90a7e16c89..f5052559de9 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index ace7e5defc0..9d82ad59fad 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gener" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contrasenya feble identificada i trobada en una filtració de dades. Utilitzeu una contrasenya única i segura per protegir el vostre compte. Esteu segur que voleu utilitzar aquesta contrasenya?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Comproveu les filtracions de dades conegudes per a aquesta contrasenya" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index f73f76f44a5..83f2840b724 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Leden" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slabé heslo bylo nalezeno mezi odhalenými hesly. K zabezpečení Vašeho účtu používejte silné a jedinečné heslo. Opravdu chcete používat toto heslo?" }, + "useThisPassword": { + "message": "Použít toto heslo" + }, + "useThisUsername": { + "message": "Použít toto uživatelské jméno" + }, "checkForBreaches": { "message": "Zkontrolovat heslo, zda nebylo odhaleno" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Pro přihlášení SSO nebyly nalezeny žádné volné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrické odemknutí je momentálně nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrické odemknutí není dostupné kvůli chybnému nastavení systémových souborů." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrické odemknutí není k dispozici, protože není povoleno pro $EMAIL$ v desktopové aplikaci Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." + }, "authorize": { "message": "Autorizovat" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 00d79c80ec8..f4e0853a933 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index b2867c4072e..ebe74818f47 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Svag adgangskode identificeret og fundet i datalæk. Brug en unik adgangskode til at beskytte din konto. Sikker på, at at denne adgangskode skal bruges?" }, + "useThisPassword": { + "message": "Anvend denne adgangskode" + }, + "useThisUsername": { + "message": "Anvend dette brugernavn" + }, "checkForBreaches": { "message": "Tjek kendte datalæk for denne adgangskode" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Ingen ledige porte fundet til SSO-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisk oplåsning er p.t. utilgængelig." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisk oplåsning er utilgængelig grundet fejlopsatte systemfiler." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisk oplåsning er utilgængelig, da det ikke er aktiveret for $EMAIL$ i Bitwarden computer-appen.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." + }, "authorize": { "message": "Godkend" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index ae20bc0931d..133d98c3faa 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kundensupport kontaktieren", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Schwaches Passwort erkannt und in einem Datendiebstahl gefunden. Verwende ein starkes und einzigartiges Passwort, um dein Konto zu schützen. Bist du sicher, dass du dieses Passwort verwenden möchtest?" }, + "useThisPassword": { + "message": "Dieses Passwort verwenden" + }, + "useThisUsername": { + "message": "Diesen Benutzernamen verwenden" + }, "checkForBreaches": { "message": "Bekannte Datendiebstähle auf dieses Passwort überprüfen" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Es konnten keine freien Ports für die SSO-Anmeldung gefunden werden." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisches Entsperren ist derzeit nicht verfügbar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisches Entsperren ist aufgrund falsch konfigurierter Systemdateien nicht verfügbar." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisches Entsperren ist nicht verfügbar, da es für $EMAIL$ in der Bitwarden Desktop-App nicht aktiviert ist.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." + }, "authorize": { "message": "Autorisieren" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 676c016c699..145f386c6b9 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ιανουάριος" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Βρέθηκε και ταυτοποιήθηκε αδύναμος κωδικός σε μια διαρροή δεδομένων. Χρησιμοποιήστε ένα ισχυρό και μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό πρόσβασης;" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Ελέγξτε γνωστές διαρροές δεδομένων για αυτόν τον κωδικό" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Δεν βρέθηκαν ελεύθερες θύρες για τη σύνδεση sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 35d5cf8c03c..bca12f16a7d 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2879,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3468,5 +3474,14 @@ }, "changeAcctEmail": { "message": "Change account email" + }, + "organizationUpgradeRequired": { + "message": "Organization upgrade required" + }, + "upgradeOrganization": { + "message": "Upgrade organization" + }, + "upgradeOrganizationDesc": { + "message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features." } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index ee1be4df2e6..acca06b8b4f 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorise" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index b8afb36f3a6..122217dae9d 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorise" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index b7fbb2fbe93..a4621439f50 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januaro" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 9cb82ceb92d..1915002b6bd 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contraseña débil encontrada en una filtración de datos. Utilice una contraseña única para proteger su cuenta. ¿Está seguro de que desea utilizar una contraseña comprometida?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Comprobar filtración de datos conocidos para esta contraseña" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 276d3565f1a..8396316416b 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Jaanuar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Tuvastati nõrk ning andmelekkes lekkinud ülemparool. Kasuta konto paremaks turvamiseks tugevamat parooli. Oled kindel, et soovid nõrga parooliga jätkata?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Otsi seda parooli teadaolevatest andmeleketest" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "SSO-ga sisselogimiseks ei leitud ühtegi vaba porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 41e8eda96c7..2daed855e52 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Urtarrila" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index fdc9ba6d31b..b79dc2d90ee 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -249,6 +249,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ژانویه" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "کلمه عبور ضعیف شناسایی و در یک نقض داده پیدا شد. از یک کلمه عبور قوی و منحصر به فرد برای محافظت از حساب خود استفاده کنید. آیا مطمئنید که می‌خواهید از این کلمه عبور استفاده کنید؟" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "نقض اطلاعات شناخته شده برای این کلمه عبور را بررسی کنید" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 69092054f28..2d0a8cae996 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tammikuu" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Havaittiin heikko ja tietovuodosta löytynyt salasana. Sinun tulisi suojata tilisi vahvalla ja ainutlaatuisella salasanalla. Haluatko varmasti käyttää tätä salasanaa?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Tarkasta esiintyykö salasanaa tunnetuissa tietovuodoissa" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Kertakirjautumiselle ei löytynyt vapaita portteja." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Valtuuta" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index a8a36cd94af..1f77b85e3c5 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Enero" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mahinang password na nakilala at nakita sa data breach. Gamitin ang malakas at natatanging password upang makaproteksyon sa iyong account. Sigurado ka ba na gusto mong gamitin ang password na ito?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Tingnan ang kilalang breaches ng data para sa password na ito" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index a353653e1e3..f0ab36ba636 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvier" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mot de passe faible identifié et trouvé dans une brèche de données. Utilisez un mot de passe robuste et unique pour protéger votre compte. Êtes-vous sûr de vouloir utiliser ce mot de passe ?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Vérifier les brèches de données connues pour ce mot de passe" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autoriser" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 323d0cd3f7b..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index dfaa195895e..df61cefe73d 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -249,6 +249,20 @@ "error": { "message": "שגיאה" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ינואר" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 13bb5edfa93..723ea0f6992 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index a87261a2027..109a1abff21 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Pogreška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "siječanj" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slaba lozinka je nađena među ukradenima tijekom krađa podataka. Za zaštitu svog računa koristi jaku i jedinstvenu lozinku. Želiš li svejedno korisiti slabu, ukradenu lozinku?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Provjeri je li lozinka ukradena prilikom krađe podataka" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Nisu nađeni slobodni portovi za SSO prijavu." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autoriziraj" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 11091cde860..3716e1e67a9 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérés siker", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "január" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Gyenge jelszó lett azonosítva és megtalálva egy adatvédelmi incidens során. A fók védelme érdekében használjunk erős és egyedi jelszót. Biztosan használni szeretnénk ezt a jelszót?" }, + "useThisPassword": { + "message": "Jelszó használata" + }, + "useThisUsername": { + "message": "Felhasználónév használata" + }, "checkForBreaches": { "message": "Az ehhez a jelszóhoz tartozó ismert adatvédelmi incidensek ellenőrzése" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Nem található szabad port az sso bejelentkezéshez." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "A biometrikus feloldás jelenleg nem érhető el." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "A biometrikus feloldás nem érhető el a rosszul konfigurált rendszerfájlok miatt." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "A biometrikus feloldás nem érhető el, mert nincs engedélyezve $EMAIL$ számára a Bitwarden asztali alkalmazásban.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." + }, "authorize": { "message": "Hitelesítés" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index b9af1c00045..d4bcded0ee8 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Periksa pelanggaran data yang diketahui untuk kata sandi ini" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index e035a13606d..c85127fce20 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Gennaio" }, @@ -1320,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copia e-mail" + "message": "Copia email" }, "copySecurityCode": { "message": "Copia codice di sicurezza", @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Password debole e trovata in una violazione dei dati. Usa una password forte e unica per proteggere il tuo account. Sei sicuro di voler usare questa password?" }, + "useThisPassword": { + "message": "Usa questa parola d'accesso" + }, + "useThisUsername": { + "message": "Usa questo nome utente" + }, "checkForBreaches": { "message": "Controlla se la tua password è presente in una violazione dei dati" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Non è stato possibile trovare nessuna porta libera per il login Sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Lo sblocco biometrico non è attualmente disponibile." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Lo sblocco biometrico non è disponibile a causa di file di sistema mal configurati." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Lo sblocco biometrico non è disponibile perché non è abilitato per $EMAIL$ nell'app desktop Bitwarden.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." + }, "authorize": { "message": "Autorizza" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 7315d74a70f..9ce8f6a3ea7 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -249,6 +249,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1月" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "入力されたパスワードは脆弱かつすでに流出済みです。アカウントを守るためより強力で一意なパスワードを使用してください。本当にこの脆弱なパスワードを使用しますか?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "このパスワードの既知のデータ流出を確認" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "SSO ログインのための空きポートが見つかりませんでした。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "認可" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 6c554a82700..bf434a6c29f 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -249,6 +249,20 @@ "error": { "message": "შეცდომა" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "იანვარი" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 323d0cd3f7b..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 6182bd3a33d..f3401477f4a 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ಜನವರಿ" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 89c5430a5fb..1aafdc4bc6d 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -249,6 +249,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "1월" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 6e07f4ceee3..11c70dd4197 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Klaida" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Sausis" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Silpnas slaptažodis nustatytas ir rastas per duomenų pažeidimą. Norėdami apsaugoti paskyrą, naudokite stiprų ir unikalų slaptažodį. Ar tikrai norite naudoti šį slaptažodį?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Patikrinti žinomus šio slaptažodžio duomenų pažeidimus" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index a3db76ac646..68495a25137 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janvāris" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Noteikta vāja parole, un tā ir atrasta datu noplūdē. Jāizmanto spēcīga un neatkārtojama parole, lai aizsargātu savu kontu. Vai tiešām izmantot šo paroli?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Meklēt šo paroli zināmās datu noplūdēs" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Netika atrasti brīvi vienotās (SSO) pieteikšanās porti." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Atslēgšana ar biometriju nav pieejama nepareizi konfigurētu sistēmas datņu dēļ." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Atslēgšana ar biometriju nav pieejama, jo tā nav iespējota $EMAIL$ Bitwarden darbvirsmas lietotnē.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." + }, "authorize": { "message": "Pilnvarot" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 49bf2c60f6c..ac9994c3134 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index d314ee44578..15d4de334eb 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -249,6 +249,20 @@ "error": { "message": "പിശക്" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ജനുവരി" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 323d0cd3f7b..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index c1713e0bb32..ca399731495 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index f264bb5f092..0ebecf3be8f 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index fb8d7ec7df5..624547d2121 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -249,6 +249,20 @@ "error": { "message": "त्रुटि" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "जनवरी" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index a23ef820ab7..6f3670afa3d 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "januari" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zwak wachtwoord geïdentificeerd en gevonden in een datalek. Gebruik een sterk en uniek wachtwoord om je account te beschermen. Weet je zeker dat je dit wachtwoord wilt gebruiken?" }, + "useThisPassword": { + "message": "Dit wachtwoord gebruiken" + }, + "useThisUsername": { + "message": "Deze gebruikersnaam gebruiken" + }, "checkForBreaches": { "message": "Bekende datalekken voor dit wachtwoord controleren" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Er zijn geen vrije poorten gevonden voor de sso-login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometrisch ontgrendelen is niet beschikbaar vanwege verkeerd geconfigureerde systeembestanden." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometrisch ontgrendelen is niet beschikbaar omdat het niet is ingeschakeld voor $EMAIL$ in de Bitwarden-desktopapp.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." + }, "authorize": { "message": "Autoriseren" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 543f20fc01d..df2f70ae839 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 2723db4f882..ea55a1c6029 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ତ୍ରୁଟି" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ଜାନୁଆରୀ" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 9cddf815463..d7b950100f5 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Styczeń" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Słabe hasło ujawnione w wyniku naruszenia ochrony danych. Użyj silnego i unikalnego hasła, aby chronić swoje konto. Czy na pewno chcesz użyć tego hasła?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Sprawdź znane naruszenia ochrony danych tego hasła" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Nie znaleziono wolnych portów dla logowania SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 3e1b8f54040..312fe4e8e89 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Verificar vazamentos de dados conhecidos para esta senha" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Nenhuma porta livre foi encontrada para o cliente final." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Autorizar" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 55b031690a8..d88862a7980 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Janeiro" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?" }, + "useThisPassword": { + "message": "Utilizar esta palavra-passe" + }, + "useThisUsername": { + "message": "Utilizar este nome de utilizador" + }, "checkForBreaches": { "message": "Verificar violações de dados conhecidas para esta palavra-passe" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Não foi possível encontrar portas livres para o início de sessão sso." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "O desbloqueio biométrico está atualmente indisponível." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "O desbloqueio biométrico não está disponível devido a ficheiros de sistema mal configurados." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "O desbloqueio biométrico não está disponível porque não está ativado para $EMAIL$ na app Bitwarden para computador.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." + }, "authorize": { "message": "Autorizar" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 0d26dbb2fc8..d4ca5e21a5e 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "ianuarie" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 056c9d32aca..46e04b77704 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Январь" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Обнаружен слабый пароль, найденный в утечке данных. Используйте надежный и уникальный пароль для защиты вашего аккаунта. Вы уверены, что хотите использовать этот пароль?" }, + "useThisPassword": { + "message": "Использовать этот пароль" + }, + "useThisUsername": { + "message": "Использовать это имя пользователя" + }, "checkForBreaches": { "message": "Проверять известные случаи утечки данных для этого пароля" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Не удалось найти свободные порты для авторизации SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Биометрическая разблокировка в настоящее время недоступна." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Биометрическая разблокировка недоступна из-за неправильно настроенных системных файлов." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Биометрическая разблокировка недоступна, потому что она не включена для $EMAIL$ в приложении Bitwarden для компьютера.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." + }, "authorize": { "message": "Разрешить" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 51625639dda..5372bf59637 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -249,6 +249,20 @@ "error": { "message": "දෝෂය" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "දුරුතු" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index cec06c3c028..07851ddc47e 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Január" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Nájdené slabé heslo v uniknuných údajoch. Na ochranu svojho účtu používajte silné a jedinečné heslo. Naozaj chcete používať toto heslo?" }, + "useThisPassword": { + "message": "Použiť toto heslo" + }, + "useThisUsername": { + "message": "Použiť toto používateľské meno" + }, "checkForBreaches": { "message": "Skontrolovať známe úniky údajov pre toto heslo" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Pre prihlásenie SSO sa nepodarilo nájsť žiadne voľné porty." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Odomykanie biometrickými údajmi je momentálne nedostupné." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Odomykanie biometrickými údajmi je nedostupné v dôsledku zle nastavených systémových súborov." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Odomykanie biometrickými údajmi je nedostupné pretože nie je povolené pre $EMAIL$ v aplikácii Bitwarden pre desktop.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Odomykanie biometrickými údajmi je z neznámych dôvodov nedostupné." + }, "authorize": { "message": "Autorizovať" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 1973f6c017b..c4fae11c815 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Napaka" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januar" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 9f9dec2dd68..03cc5b8c265 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Јануар" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Идентификована је слаба лозинка и пронађена у упаду података. Користите јаку и јединствену лозинку да заштитите свој налог. Да ли сте сигурни да желите да користите ову лозинку?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Проверите познате упада података за ову лозинку" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Нису пронађени портови за SSO пријаву." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Ауторизуј" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index c9a463246dd..920981908fa 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Januari" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Lösenordet är svagt och avslöjades vid ett dataintrång. Använd ett starkt och unikt lösenord för att skydda ditt konto. Är det säkert att du vill använda detta lösenord?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kontrollera kända dataintrång för detta lösenord" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 323d0cd3f7b..1a02e5db4e7 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "January" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 16961afcc57..7f7e373132c 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -249,6 +249,20 @@ "error": { "message": "ข้อผิดพลาด" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "มกราคม" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 8357f8616bc..2fbd751ddb8 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Ocak" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Hem zayıf hem de veri ihlalinde yer alan bir tespit edildi. Hesabınızı korumak için güçlü bir parola seçin ve o parolayı başka yerlerde kullanmayın. Bu parolayı kullanmak istediğinizden emin misiniz?" }, + "useThisPassword": { + "message": "Bu parolayı kullan" + }, + "useThisUsername": { + "message": "Bu kullanıcı adını kullan" + }, "checkForBreaches": { "message": "Bilinen veri ihlallerinde bu parolayı kontrol et" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "SSO girişi için açık port bulunamadı." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Yetkilendir" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 4103e8b28a6..db0259adc05 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Січень" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Виявлено слабкий пароль, який знайдено у витоку даних. Використовуйте надійний та унікальний пароль для захисту свого облікового запису. Ви дійсно хочете використати цей пароль?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Перевірити відомі витоки даних для цього пароля" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Не знайдено вільних портів для цього входу SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Авторизувати" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index e514124414b..07dcdfd373c 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -249,6 +249,20 @@ "error": { "message": "Lỗi" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "Tháng 1" }, @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mật khẩu yếu này đã bị rò rỉ trong một vụ tấn công dữ liệu. Dùng mật khẩu mới và an toàn để bảo vệ tài khoản bạn. Bạn có chắc muốn sử dụng mật khẩu đã bị rò rỉ?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "Kiểm tra mật khẩu có lộ trong các vụ rò rỉ dữ liệu hay không" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "Không thể tìm thấy cổng trống để đăng nhập SSO." }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "Biometric unlock is currently unavailable." + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "Biometric unlock is unavailable due to misconfigured system files." + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "Biometric unlock is currently unavailable for an unknown reason." + }, "authorize": { "message": "Authorize" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 9bff79aa857..c9046b34a55 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -249,6 +249,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -975,13 +989,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -1008,7 +1022,7 @@ "message": "账户" }, "loading": { - "message": "加载中…" + "message": "正在加载..." }, "lockVault": { "message": "锁定密码库" @@ -2255,7 +2269,7 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,您必须立即更新它。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -2495,7 +2509,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2505,7 +2519,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护你的账户。确定要使用这个密码吗?" }, + "useThisPassword": { + "message": "使用此密码" + }, + "useThisUsername": { + "message": "使用此用户名" + }, "checkForBreaches": { "message": "检查已知的数据泄露是否包含此密码" }, @@ -3362,6 +3382,30 @@ "ssoError": { "message": "找不到用于 SSO 登录的可用端口。" }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物识别解锁当前不可用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由于系统文件配置错误,生物识别解锁不可用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "生物识别解锁不可用,因为在 Bitwarden 桌面 App 中没有为 $EMAIL$ 启用生物识别解锁。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "由于某个未知的原因,生物识别解锁当前不可用。" + }, "authorize": { "message": "批准" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 3c7884a22c5..a9206a50df6 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -27,7 +27,7 @@ "message": "安全筆記" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH 金鑰" }, "folders": { "message": "資料夾" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "歡迎回來" }, "moveToOrgDesc": { "message": "選擇您希望將這個項目移動到哪個組織。項目的擁有權將會轉移到該組織。一經移動,您將不再是此項目的直接擁有者。" @@ -181,16 +181,16 @@ "message": "地址" }, "sshPrivateKey": { - "message": "Private key" + "message": "私密金鑰" }, "sshPublicKey": { - "message": "Public key" + "message": "公開金鑰" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "指紋" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "金鑰類型" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -208,19 +208,19 @@ "message": "一組SSH金鑰已在之前生成了" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "您輸入的密碼錯誤。" }, "importSshKey": { - "message": "Import" + "message": "匯入" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "確認密碼" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "輸入 SSH 金鑰的密碼" }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "請輸入密碼" }, "sshAgentUnlockRequired": { "message": "請解鎖密碼庫以核准SSh金鑰的請求" @@ -229,7 +229,7 @@ "message": "SSH金鑰請求超時" }, "enableSshAgent": { - "message": "啟用SSH代理" + "message": "啟用 SSH 代理程式" }, "enableSshAgentDesc": { "message": "啟用SSBitwardenH代理以從 Bitwarden 密碼庫簽發SSH請求" @@ -249,6 +249,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "解密發生錯誤" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 無法解密您密碼庫中下面的項目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "聯絡客戶支援部門", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "來避免更多資料遺失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "january": { "message": "一月" }, @@ -510,7 +524,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "包含小寫字元", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -642,10 +656,10 @@ "message": "以通行密鑰 (passkey) 登入" }, "loginWithDevice": { - "message": "Log in with device" + "message": "使用裝置登入" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "使用單一登入" }, "submit": { "message": "送出" @@ -666,7 +680,7 @@ "message": "主密碼提示(選用)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "如果您忘記了密碼,可以傳送密碼提示到您的電子郵件。$CURRENT$ / 最多 $MAXIMUM$ 個字元", "placeholders": { "current": { "content": "$1", @@ -703,7 +717,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "設定主密碼以完成加入這個組織" }, "settings": { "message": "設定" @@ -712,13 +726,13 @@ "message": "電子郵件帳戶:" }, "requestHint": { - "message": "Request hint" + "message": "請求提示" }, "requestPasswordHint": { "message": "主密碼提示" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "輸入您帳號的電子郵件,您的密碼提示會傳送給您" }, "passwordHint": { "message": "密碼提示" @@ -764,7 +778,7 @@ "message": "帳戶已建立!現在可以登入了。" }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { "message": "你已經登入!" @@ -854,17 +868,17 @@ "message": "驗證器應用程式" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "輸入驗證器應用程式產生的驗證碼,例如 Bitwarden 驗證器。", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "YubiKey OTP 安全金鑰" }, "yubiKeyDesc": { "message": "使用 YubiKey 來存取您的帳戶。支援 YubiKey 4、4 Nano、4C、以及 NEO 裝置。" }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "輸入 Duo 應用程式產生的驗證碼。", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -881,7 +895,7 @@ "message": "電子郵件" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "輸入寄送到您電子郵件信箱的驗證碼。" }, "loginUnavailable": { "message": "登入無法使用" @@ -902,13 +916,13 @@ "message": "指定您本地托管的 Bitwarden 安裝之基礎 URL。" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "指定您自建的 Bitwarden 伺服器的網域 URL。例如:https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "適用於進階設定。您可以單獨指定各個服務的網域 URL。" }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "您必須新增伺服器網域 URL 或至少一個自定義環境。" }, "customEnvironment": { "message": "自訂環境" @@ -920,13 +934,13 @@ "message": "伺服器 URL" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "驗證逾時" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "驗證工作階段因時間過久已逾時。請重試登入。" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "自建伺服器 URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -969,22 +983,22 @@ "message": "已登出" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "您已經登出了您的帳號。" }, "loginExpired": { "message": "您的登入會話已過期。" }, "restartRegistration": { - "message": "Restart registration" + "message": "重新啟動註冊" }, "expiredLink": { - "message": "Expired link" + "message": "過期連結" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "請重新啟動註冊流程或是重試登入。" }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "您可能已經有帳號" }, "logOutConfirmation": { "message": "您確定要登出嗎?" @@ -1041,10 +1055,10 @@ "message": "變更主密碼" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "前往網頁應用程式嗎?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式上變更主密碼。" }, "fingerprintPhrase": { "message": "指紋短語", @@ -1073,16 +1087,16 @@ "message": "您的密碼庫已鎖定。請驗證身分以繼續。" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "您的帳戶已被鎖定。" }, "or": { - "message": "or" + "message": "或" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "使用生物辨識解鎖" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "使用主密碼解鎖" }, "unlock": { "message": "解鎖" @@ -1113,7 +1127,7 @@ "message": "密碼庫逾時時間" }, "vaultTimeout1": { - "message": "Timeout" + "message": "逾時" }, "vaultTimeoutDesc": { "message": "選擇密碼庫何時執行密碼庫逾時動作。" @@ -1320,7 +1334,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "複製電子郵件地址" }, "copySecurityCode": { "message": "複製安全代碼", @@ -1369,7 +1383,7 @@ "message": "您可以在 bitwarden.com 網頁版密碼庫購買進階會員資格。現在要前往嗎?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "您可以在 Bitwarden 網頁應用程式的帳號設定中購買進階版。" }, "premiumCurrentMember": { "message": "您目前是進階會員!" @@ -1393,13 +1407,13 @@ "message": "密碼歷史記錄" }, "generatorHistory": { - "message": "Generator history" + "message": "產生器歷史記錄" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "清除產生器歷史記錄" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "若繼續,所有產生器曾經產生的記錄會被刪除。您確定要繼續?" }, "clear": { "message": "清除", @@ -1409,13 +1423,13 @@ "message": "沒有可列出的密碼。" }, "clearHistory": { - "message": "Clear history" + "message": "清除歷史紀錄" }, "nothingToShow": { - "message": "Nothing to show" + "message": "沒有可顯示的內容" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "您最近未產生任何密碼" }, "undo": { "message": "復原" @@ -1492,13 +1506,13 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "複製成功" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "存取權杖更新失敗" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "未找到存取權杖或 API 密鑰。請重試登出再登入。" }, "help": { "message": "說明" @@ -1588,7 +1602,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "匯出自" }, "exportVault": { "message": "匯出密碼庫" @@ -1720,7 +1734,7 @@ "message": "無效的 PIN 碼。" }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "輸入太多無效 PIN 碼。 正在登出。" }, "unlockWithWindowsHello": { "message": "使用 Windows Hello 解鎖" @@ -1729,7 +1743,7 @@ "message": "額外的 Windows Hello 設定" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "使用系統驗證解鎖" }, "windowsHelloConsentMessage": { "message": "驗證 Bitwarden。" @@ -1747,7 +1761,7 @@ "message": "啟動應用程式時詢問 Windows Hello" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "在啟動時詢問系統驗證" }, "autoPromptTouchId": { "message": "啟動應用程式時要求 Touch ID" @@ -1759,7 +1773,7 @@ "message": "為提升安全,建議使用。" }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "重啟後使用主密碼鎖定" }, "deleteAccount": { "message": "刪除帳戶" @@ -1771,7 +1785,7 @@ "message": "刪除您的帳戶是永久性的。並且無法復原。" }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "無法刪除帳號" }, "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." @@ -2865,6 +2879,12 @@ "weakAndBreachedMasterPasswordDesc": { "message": "密碼強度不足,且該密碼已洩露。請使用一個強度足夠和獨特的密碼來保護您的帳戶。您確定要使用這個密碼嗎?" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisUsername": { + "message": "Use this username" + }, "checkForBreaches": { "message": "檢查外洩的密碼資料庫中是否包含此密碼" }, @@ -2908,10 +2928,10 @@ "message": "裝置需要取得核准。請在下面選擇一個核准選項:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "需要核准裝置" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "選擇下面的一個核准選項" }, "rememberThisDevice": { "message": "記住這個裝置" @@ -2966,7 +2986,7 @@ "message": "缺少使用者電子郵件地址" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "未找到使用中帳號的電子郵件。正在將您登出。" }, "deviceTrusted": { "message": "裝置已信任" @@ -3072,7 +3092,7 @@ "message": "子選單" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "切換側邊欄" }, "skipToContent": { "message": "跳至內容" @@ -3130,16 +3150,16 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "連接到 Duo 服務時發生錯誤。使用不同的兩階段認證或聯繫 Duo 來獲得支援。" }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "啟動 Duo 並依照步驟完成登入。" }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的帳號要求使用 Duo 兩步驟驗證登入。" }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "使用瀏覽器啟動 Duo" }, "importFormatError": { "message": "資料格式不正確。請檢查匯入檔案後再試一次。" @@ -3154,7 +3174,7 @@ "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, "destination": { - "message": "Destination" + "message": "目的" }, "learnAboutImportOptions": { "message": "瞭解更多匯入選項" @@ -3293,7 +3313,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "成功" }, "troubleshooting": { "message": "疑難排解" @@ -3311,13 +3331,13 @@ "message": "密碼金鑰已移除" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "指定目標集合時發生錯誤。" }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "指定目標資料夾時發生錯誤。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "檢視 $NAME$ 中的項目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3327,7 +3347,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "回到 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3337,11 +3357,11 @@ } }, "back": { - "message": "Back", + "message": "返回", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "移除 $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3351,67 +3371,91 @@ } }, "data": { - "message": "Data" + "message": "資料" }, "fileSends": { - "message": "File Sends" + "message": "檔案 Send" }, "textSends": { - "message": "Text Sends" + "message": "文字 Sends" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "無法找到可用於 SSO 登入的空閒連接埠。" + }, + "biometricsStatusHelptextUnlockNeeded": { + "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" + }, + "biometricsStatusHelptextHardwareUnavailable": { + "message": "生物辨識解鎖暫時無法使用。" + }, + "biometricsStatusHelptextAutoSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextManualSetupNeeded": { + "message": "由於系統檔案不正確,生物辨識解鎖無法使用。" + }, + "biometricsStatusHelptextNotEnabledLocally": { + "message": "由於未在 Bitwarden 桌面應用程式的 $EMAIL$ 帳號上啟動,生物辨識解鎖無法使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, + "biometricsStatusHelptextUnavailableReasonUnknown": { + "message": "基於不明原因,生物辨識解鎖無法使用。" }, "authorize": { - "message": "Authorize" + "message": "授權" }, "deny": { - "message": "Deny" + "message": "拒絕" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "確認 SSH 密鑰使用" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "正在請求存取權限到" }, "unknownApplication": { - "message": "An application" + "message": "應用程式" }, "sshKeyPasswordUnsupported": { - "message": "Importing password protected SSH keys is not yet supported" + "message": "匯入密碼保護的 SSH 密鑰暫時還未支援" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "SSH 密鑰不正確" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "SSH 密鑰類型不支援" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "從剪貼簿中匯入密鑰" }, "sshKeyPasted": { - "message": "SSH key imported successfully" + "message": "SSH 密鑰成功匯入" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "檔案已儲存到裝置。在您的裝置上管理下載檔案。" }, "importantNotice": { - "message": "Important notice" + "message": "重要通知" }, "setupTwoStepLogin": { - "message": "Set up two-step login" + "message": "啟動兩階段登入" }, "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." + "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" }, "newDeviceVerificationNoticeContentPage2": { - "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." + "message": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" }, "remindMeLater": { - "message": "Remind me later" + "message": "稍後再提醒我" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", "placeholders": { "email": { "content": "$1", @@ -3420,15 +3464,15 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "不,我不行" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "是,我可以存取我的電子郵件位址" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "啟動兩階段登入" }, "changeAcctEmail": { - "message": "Change account email" + "message": "更改帳號電子郵件位址" } } diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 6915a18deda..107d546811c 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -405,6 +405,8 @@ export class NativeMessagingMain { this.logService.info(`Error reading preferences: ${e}`); } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Browser is not installed, we can just skip it } diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index f93d9059bc7..d878e1af2aa 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.1.2", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 39e16815020..08bdd745063 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.0", + "version": "2025.1.2", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index 62200962dca..0443034f551 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -10,8 +10,8 @@ import { DialogModule, FormFieldModule, IconButtonModule, + DialogService, } from "@bitwarden/components"; -import { DialogService } from "@bitwarden/components/src/dialog"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; export interface ApproveSshRequestParams { diff --git a/apps/desktop/src/platform/flags.ts b/apps/desktop/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/desktop/src/platform/flags.ts +++ b/apps/desktop/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/desktop/src/platform/services/ssh-agent.service.ts b/apps/desktop/src/platform/services/ssh-agent.service.ts index e860ebe1db5..d4c7c5f460e 100644 --- a/apps/desktop/src/platform/services/ssh-agent.service.ts +++ b/apps/desktop/src/platform/services/ssh-agent.service.ts @@ -45,6 +45,8 @@ export class SshAgentService implements OnDestroy { SSH_VAULT_UNLOCK_REQUEST_TIMEOUT = 60_000; SSH_REQUEST_UNLOCK_POLLING_INTERVAL = 100; + private isFeatureFlagEnabled = false; + private destroy$ = new Subject(); constructor( @@ -65,21 +67,31 @@ export class SshAgentService implements OnDestroy { .getFeatureFlag$(FeatureFlag.SSHAgent) .pipe( concatMap(async (enabled) => { - if (enabled && !(await ipc.platform.sshAgent.isLoaded())) { - return this.initSshAgent(); + this.isFeatureFlagEnabled = enabled; + if (!(await ipc.platform.sshAgent.isLoaded()) && enabled) { + await ipc.platform.sshAgent.init(); } }), takeUntil(this.destroy$), ) .subscribe(); - } - private async initSshAgent() { - await ipc.platform.sshAgent.init(); + await this.initListeners(); + } + private async initListeners() { this.messageListener .messages$(new CommandDefinition("sshagent.signrequest")) .pipe( + withLatestFrom(this.desktopSettingsService.sshAgentEnabled$), + concatMap(async ([message, enabled]) => { + if (!enabled) { + await ipc.platform.sshAgent.signRequestResponse(message.requestId as number, false); + } + return { message, enabled }; + }), + filter(({ enabled }) => enabled), + map(({ message }) => message), withLatestFrom(this.authService.activeAccountStatus$), // This switchMap handles unlocking the vault if it is locked: // - If the vault is locked, we will wait for it to be unlocked. @@ -179,18 +191,30 @@ export class SshAgentService implements OnDestroy { this.accountService.activeAccount$.pipe(skip(1), takeUntil(this.destroy$)).subscribe({ next: (account) => { + if (!this.isFeatureFlagEnabled) { + return; + } + this.logService.info("Active account changed, clearing SSH keys"); ipc.platform.sshAgent .clearKeys() .catch((e) => this.logService.error("Failed to clear SSH keys", e)); }, error: (e: unknown) => { + if (!this.isFeatureFlagEnabled) { + return; + } + this.logService.error("Error in active account observable", e); ipc.platform.sshAgent .clearKeys() .catch((e) => this.logService.error("Failed to clear SSH keys", e)); }, complete: () => { + if (!this.isFeatureFlagEnabled) { + return; + } + this.logService.info("Active account observable completed, clearing SSH keys"); ipc.platform.sshAgent .clearKeys() @@ -204,11 +228,23 @@ export class SshAgentService implements OnDestroy { ]) .pipe( concatMap(async ([, enabled]) => { + if (!this.isFeatureFlagEnabled) { + return; + } + if (!enabled) { await ipc.platform.sshAgent.clearKeys(); return; } + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + const authStatus = await firstValueFrom( + this.authService.authStatusFor$(activeAccount.id), + ); + if (authStatus !== AuthenticationStatus.Unlocked) { + return; + } + const ciphers = await this.cipherService.getAllDecrypted(); if (ciphers == null) { await ipc.platform.sshAgent.lock(); diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index ea1e7e76c56..74f80785fca 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -188,7 +188,7 @@ export class BiometricMessageHandlerService { appId, ); } - // TODO: legacy, remove after 2025.01 + // TODO: legacy, remove after 2025.3 case BiometricsCommands.IsAvailable: { const available = (await this.biometricsService.getBiometricsStatus()) == BiometricsStatus.Available; @@ -200,7 +200,7 @@ export class BiometricMessageHandlerService { appId, ); } - // TODO: legacy, remove after 2025.01 + // TODO: legacy, remove after 2025.3 case BiometricsCommands.Unlock: { const isTemporarilyDisabled = (await this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId)) && @@ -265,6 +265,8 @@ export class BiometricMessageHandlerService { } else { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } @@ -342,6 +344,8 @@ export class BiometricMessageHandlerService { appId, ); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send( { command: BiometricsCommands.UnlockWithBiometricsForUser, messageId, response: false }, diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index 9a914f238b5..fa5c2f4d9f7 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -130,6 +130,8 @@ export class DuckDuckGoMessageHandlerService { sharedKey: Utils.fromBufferToB64(encryptedSecret), }, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.sendResponse({ messageId: messageId, @@ -153,6 +155,8 @@ export class DuckDuckGoMessageHandlerService { await this.encryptedMessageHandlerService.responseDataForCommand(decryptedCommandData); await this.sendEncryptedResponse(message, { command, payload: responseData }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 09c0fe2a07c..43c4b9065a7 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -178,6 +178,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } @@ -222,6 +224,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html index a294ce1f63f..d16f8e4f1cb 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html @@ -4,6 +4,7 @@ - diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts index abf74371912..ae6f031005e 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -14,6 +14,7 @@ import { CredentialGeneratorHistoryDialogComponent, GeneratorModule, } from "@bitwarden/generator-components"; +import { AlgorithmInfo } from "@bitwarden/generator-core"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; type CredentialGeneratorParams = { @@ -38,12 +39,17 @@ type CredentialGeneratorParams = { }) export class CredentialGeneratorDialogComponent { credentialValue?: string; + buttonLabel?: string; constructor( @Inject(DIALOG_DATA) protected data: CredentialGeneratorParams, private dialogService: DialogService, ) {} + algorithm = (selected: AlgorithmInfo) => { + this.buttonLabel = selected.useGeneratedValue; + }; + applyCredentials = () => { this.data.onCredentialGenerated(this.credentialValue); }; diff --git a/apps/desktop/src/vault/app/vault/view.component.html b/apps/desktop/src/vault/app/vault/view.component.html index 59e609312d7..f589ba53046 100644 --- a/apps/desktop/src/vault/app/vault/view.component.html +++ b/apps/desktop/src/vault/app/vault/view.component.html @@ -186,6 +186,16 @@

+
+
+ {{ "verificationCodeTotp" | i18n }} + + {{ "organizationUpgradeRequired" | i18n }} + + +
+
diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 23b85eceda2..d3e8fff3495 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -157,4 +157,10 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.messagingService.send("premiumRequired"); } } + + upgradeOrganization() { + this.messagingService.send("upgradeOrganization", { + organizationId: this.cipher.organizationId, + }); + } } diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index a561b93b21a..bf3b67c74ad 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -5,6 +5,7 @@ config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", + "../../libs/key-management/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts,mdx}", ]; diff --git a/apps/web/package.json b/apps/web/package.json index 42432de29a4..73ecd8c3cb7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.1.0", + "version": "2025.1.1", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js index c4513687e89..c39e7ea0355 100644 --- a/apps/web/postcss.config.js +++ b/apps/web/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports, no-undef */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index 1080880a466..9d06be92eb8 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -167,6 +167,8 @@ export abstract class BaseEventsComponent { let dates: string[] = null; try { dates = this.eventService.formatDateFilters(this.start, this.end); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 1ba1431cbd5..58efc7348e1 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -113,6 +113,8 @@ export class EntityEventsComponent implements OnInit { this.filterFormGroup.value.start, this.filterFormGroup.value.end, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index c77c8fc935f..5e81e4ee711 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -23,14 +23,19 @@

{{ "inviteUserDesc" | i18n }}

- + {{ "email" | i18n }} - {{ - "inviteMultipleEmailDesc" - | i18n - : (organization.productTierType === ProductTierType.TeamsStarter ? "10" : "20") + + {{ + "inviteMultipleEmailDesc" | i18n: remaining.seats }} + + {{ + "inviteSingleEmailDesc" | i18n + }} + + {{ "inviteZeroEmailDesc" | i18n }}
diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index f4e2fbccbeb..4f43dacad32 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -68,7 +68,7 @@ export interface MemberDialogParams { usesKeyConnector: boolean; isOnSecretsManagerStandalone: boolean; initialTab?: MemberDialogTab; - numConfirmedMembers: number; + numSeatsUsed: number; managedByOrganization?: boolean; } @@ -94,6 +94,7 @@ export class MemberDialogComponent implements OnDestroy { PermissionMode = PermissionMode; showNoMasterPasswordWarning = false; isOnSecretsManagerStandalone: boolean; + remainingSeats$: Observable; protected organization$: Observable; protected collectionAccessItems: AccessItemView[] = []; @@ -260,6 +261,10 @@ export class MemberDialogComponent implements OnDestroy { this.loading = false; }); + + this.remainingSeats$ = this.organization$.pipe( + map((organization) => organization.seats - this.params.numSeatsUsed), + ); } private setFormValidators(organization: Organization) { @@ -453,7 +458,7 @@ export class MemberDialogComponent implements OnDestroy { } if ( organization.hasReseller && - this.params.numConfirmedMembers + emails.length > organization.seats + this.params.numSeatsUsed + emails.length > organization.seats ) { this.formGroup.controls.emails.setErrors({ tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") }, diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 3d10a4a07b3..703f187b223 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -510,6 +510,11 @@ export class MembersComponent extends BaseMembersComponent return; } + const numSeatsUsed = + this.dataSource.confirmedUserCount + + this.dataSource.invitedUserCount + + this.dataSource.acceptedUserCount; + const dialog = openUserAddEditDialog(this.dialogService, { data: { name: this.userNamePipe.transform(user), @@ -519,7 +524,7 @@ export class MembersComponent extends BaseMembersComponent usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: initialTab, - numConfirmedMembers: this.dataSource.confirmedUserCount, + numSeatsUsed, managedByOrganization: user?.managedByOrganization, }, }); diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index b3c046d1fef..031bd7bf180 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -170,6 +170,8 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.router.navigate(["/"]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { if (this.showNewOrganization) { const dialog = openDeleteOrganizationDialog(this.dialogService, { diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 16c783f3a5a..ee9f87bc2cd 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -231,6 +231,20 @@ export class AppComponent implements OnDestroy, OnInit { } break; } + case "syncOrganizationCollectionSettingChanged": { + const { organizationId, limitCollectionCreation, limitCollectionDeletion } = message; + const organizations = await firstValueFrom(this.organizationService.organizations$); + const organization = organizations.find((org) => org.id === organizationId); + + if (organization) { + await this.organizationService.upsert({ + ...organization, + limitCollectionCreation: limitCollectionCreation, + limitCollectionDeletion: limitCollectionDeletion, + }); + } + break; + } default: break; } diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts index 6cc94ef2d11..1c7d870175d 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.spec.ts @@ -117,14 +117,7 @@ describe("EmergencyAccessService", () => { const granteeId = "grantee-id"; const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; - const mockPublicKeyB64 = "some-public-key-in-base64"; - - // const publicKey = Utils.fromB64ToArray(publicKeyB64); - - const mockUserPublicKeyResponse = new UserKeyResponse({ - UserId: granteeId, - PublicKey: mockPublicKeyB64, - }); + const publicKey = new Uint8Array(64); const mockUserPublicKeyEncryptedUserKey = new EncString( EncryptionType.AesCbc256_HmacSha256_B64, @@ -132,14 +125,13 @@ describe("EmergencyAccessService", () => { ); keyService.getUserKey.mockResolvedValueOnce(mockUserKey); - apiService.getUserPublicKey.mockResolvedValueOnce(mockUserPublicKeyResponse); encryptService.rsaEncrypt.mockResolvedValueOnce(mockUserPublicKeyEncryptedUserKey); emergencyAccessApiService.postEmergencyAccessConfirm.mockResolvedValueOnce(); // Act - await emergencyAccessService.confirm(id, granteeId); + await emergencyAccessService.confirm(id, granteeId, publicKey); // Assert expect(emergencyAccessApiService.postEmergencyAccessConfirm).toHaveBeenCalledWith(id, { diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts index acdf7623f9b..62a59da2995 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts @@ -153,14 +153,13 @@ export class EmergencyAccessService * Intended for grantor. * @param id emergency access id * @param token secret token provided in email + * @param publicKey public key of grantee */ - async confirm(id: string, granteeId: string) { + async confirm(id: string, granteeId: string, publicKey: Uint8Array): Promise { const userKey = await this.keyService.getUserKey(); if (!userKey) { throw new Error("No user key found"); } - const publicKeyResponse = await this.apiService.getUserPublicKey(granteeId); - const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); try { this.logService.debug( diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index 9c6296c22a9..1180c1a3542 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -4,10 +4,8 @@ import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, OnInit, Inject } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -21,6 +19,8 @@ type EmergencyAccessConfirmDialogData = { userId: string; /** traces a unique emergency request */ emergencyAccessId: string; + /** user public key */ + publicKey: Uint8Array; }; @Component({ selector: "emergency-access-confirm", @@ -36,7 +36,6 @@ export class EmergencyAccessConfirmComponent implements OnInit { constructor( @Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData, private formBuilder: FormBuilder, - private apiService: ApiService, private keyService: KeyService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, private logService: LogService, @@ -45,13 +44,12 @@ export class EmergencyAccessConfirmComponent implements OnInit { async ngOnInit() { try { - const publicKeyResponse = await this.apiService.getUserPublicKey(this.params.userId); - if (publicKeyResponse != null) { - const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); - const fingerprint = await this.keyService.getFingerprint(this.params.userId, publicKey); - if (fingerprint != null) { - this.fingerprint = fingerprint.join("-"); - } + const fingerprint = await this.keyService.getFingerprint( + this.params.userId, + this.params.publicKey, + ); + if (fingerprint != null) { + this.fingerprint = fingerprint.join("-"); } } catch (e) { this.logService.error(e); diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index 5271e50c9a3..73e32add5c2 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -4,6 +4,7 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { lastValueFrom, Observable, firstValueFrom, switchMap } from "rxjs"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -13,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService, ToastService } from "@bitwarden/components"; import { EmergencyAccessService } from "../../emergency-access"; @@ -70,6 +72,7 @@ export class EmergencyAccessComponent implements OnInit { billingAccountProfileStateService: BillingAccountProfileStateService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, private toastService: ToastService, + private apiService: ApiService, private accountService: AccountService, ) { this.canAccessPremium$ = this.accountService.activeAccount$.pipe( @@ -147,6 +150,9 @@ export class EmergencyAccessComponent implements OnInit { return; } + const publicKeyResponse = await this.apiService.getUserPublicKey(contact.granteeId); + const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); + const autoConfirm = await firstValueFrom( this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$, ); @@ -156,11 +162,12 @@ export class EmergencyAccessComponent implements OnInit { name: this.userNamePipe.transform(contact), emergencyAccessId: contact.id, userId: contact?.granteeId, + publicKey, }, }); const result = await lastValueFrom(dialogRef.closed); if (result === EmergencyAccessConfirmDialogResult.Confirmed) { - await this.emergencyAccessService.confirm(contact.id, contact.granteeId); + await this.emergencyAccessService.confirm(contact.id, contact.granteeId, publicKey); updateUser(); this.toastService.showToast({ variant: "success", @@ -171,7 +178,11 @@ export class EmergencyAccessComponent implements OnInit { return; } - this.actionPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId); + this.actionPromise = this.emergencyAccessService.confirm( + contact.id, + contact.granteeId, + publicKey, + ); await this.actionPromise; updateUser(); diff --git a/apps/web/src/app/auth/settings/security/device-management.component.html b/apps/web/src/app/auth/settings/security/device-management.component.html index 6bae88fac51..37827a33afe 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.html +++ b/apps/web/src/app/auth/settings/security/device-management.component.html @@ -21,7 +21,7 @@

{{ "devices" | i18n }}

-

{{ "deviceListDescription" | i18n }}

+

{{ "deviceListDescriptionTemp" | i18n }}

@@ -48,28 +48,43 @@

{{ "devices" | i18n }}

- {{ row.displayName }} - - {{ "trusted" | i18n }} - + + + {{ row.displayName }} + + + + {{ "needsApproval" | i18n }} + + + + {{ row.displayName }} + + {{ "trusted" | i18n }} + +
{{ "currentSession" | i18n }} - {{ + {{ "requestPending" | i18n }} {{ row.firstLogin | date: "medium" }} - + diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts index 65f2afc250e..e22122ad9ae 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.ts +++ b/apps/web/src/app/auth/settings/security/device-management.component.ts @@ -1,10 +1,14 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { firstValueFrom } from "rxjs"; -import { switchMap } from "rxjs/operators"; +import { combineLatest, firstValueFrom } from "rxjs"; +import { LoginApprovalComponent } from "@bitwarden/auth/angular"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; +import { + DevicePendingAuthRequest, + DeviceResponse, +} from "@bitwarden/common/auth/abstractions/devices/responses/device.response"; import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; import { DeviceType, DeviceTypeMetadata } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -26,7 +30,8 @@ interface DeviceTableData { loginStatus: string; firstLogin: Date; trusted: boolean; - devicePendingAuthRequest: object | null; + devicePendingAuthRequest: DevicePendingAuthRequest | null; + hasPendingAuthRequest: boolean; } /** @@ -52,28 +57,25 @@ export class DeviceManagementComponent { private toastService: ToastService, private validationService: ValidationService, ) { - this.devicesService - .getCurrentDevice$() - .pipe( - takeUntilDestroyed(), - switchMap((currentDevice) => { - this.currentDevice = new DeviceView(currentDevice); - return this.devicesService.getDevices$(); - }), - ) + combineLatest([this.devicesService.getCurrentDevice$(), this.devicesService.getDevices$()]) + .pipe(takeUntilDestroyed()) .subscribe({ - next: (devices) => { - this.dataSource.data = devices.map((device) => { + next: ([currentDevice, devices]: [DeviceResponse, Array]) => { + this.currentDevice = new DeviceView(currentDevice); + + this.dataSource.data = devices.map((device: DeviceView): DeviceTableData => { return { id: device.id, type: device.type, displayName: this.getHumanReadableDeviceType(device.type), loginStatus: this.getLoginStatus(device), - devicePendingAuthRequest: device.response.devicePendingAuthRequest, firstLogin: new Date(device.creationDate), trusted: device.response.isTrusted, + devicePendingAuthRequest: device.response.devicePendingAuthRequest, + hasPendingAuthRequest: this.hasPendingAuthRequest(device.response), }; }); + this.loading = false; }, error: () => { @@ -176,15 +178,36 @@ export class DeviceManagementComponent { /** * Check if a device has a pending auth request - * @param device - The device + * @param device - The device response * @returns True if the device has a pending auth request, false otherwise */ - protected hasPendingAuthRequest(device: DeviceTableData): boolean { + private hasPendingAuthRequest(device: DeviceResponse): boolean { return ( device.devicePendingAuthRequest !== undefined && device.devicePendingAuthRequest !== null ); } + /** + * Open a dialog to approve or deny a pending auth request for a device + */ + async managePendingAuthRequest(device: DeviceTableData) { + if (device.devicePendingAuthRequest === undefined || device.devicePendingAuthRequest === null) { + return; + } + + const dialogRef = LoginApprovalComponent.open(this.dialogService, { + notificationId: device.devicePendingAuthRequest.id, + }); + + const result = await firstValueFrom(dialogRef.closed); + + if (result !== undefined && typeof result === "boolean") { + // auth request approved or denied so reset + device.devicePendingAuthRequest = null; + device.hasPendingAuthRequest = false; + } + } + /** * Remove a device * @param device - The device diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 902cac9c771..89c0075ba0d 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -30,7 +30,7 @@ }} -
+
{ - this.organizationId = params.organizationId; - await this.load(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.showUpdatedSubscriptionStatusSection$ = this.configService.getFeatureFlag$( - FeatureFlag.AC1795_UpdatedSubscriptionStatusSection, - ); } ngOnDestroy() { diff --git a/apps/web/src/app/billing/services/reseller-warning.service.ts b/apps/web/src/app/billing/services/reseller-warning.service.ts index bfd5be3233a..2c59ebafe05 100644 --- a/apps/web/src/app/billing/services/reseller-warning.service.ts +++ b/apps/web/src/app/billing/services/reseller-warning.service.ts @@ -33,7 +33,7 @@ export class ResellerWarningService { return { type: "warning", message: this.i18nService.t( - "resellerPastDueWarning", + "resellerPastDueWarningMsg", organization.providerName, this.formatDate(gracePeriodEnd), ), @@ -50,7 +50,7 @@ export class ResellerWarningService { return { type: "info", message: this.i18nService.t( - "resellerOpenInvoiceWarning", + "resellerOpenInvoiceWarningMgs", organization.providerName, this.formatDate(organizationBillingMetadata.invoiceCreatedDate), this.formatDate(organizationBillingMetadata.invoiceDueDate), @@ -68,7 +68,7 @@ export class ResellerWarningService { return { type: "info", message: this.i18nService.t( - "resellerRenewalWarning", + "resellerRenewalWarningMsg", organization.providerName, this.formatDate(organizationBillingMetadata.subPeriodEndDate), ), diff --git a/apps/web/src/app/platform/web-sdk-client-factory.ts b/apps/web/src/app/platform/web-sdk-client-factory.ts index 2ebb2bcc10f..0dd43ecbb92 100644 --- a/apps/web/src/app/platform/web-sdk-client-factory.ts +++ b/apps/web/src/app/platform/web-sdk-client-factory.ts @@ -27,6 +27,8 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } diff --git a/apps/web/src/app/tools/send/send-access-file.component.ts b/apps/web/src/app/tools/send/send-access-file.component.ts index 0b2a971bbe8..b55e955f355 100644 --- a/apps/web/src/app/tools/send/send-access-file.component.ts +++ b/apps/web/src/app/tools/send/send-access-file.component.ts @@ -70,6 +70,8 @@ export class SendAccessFileComponent { blobData: decBuf, downloadMethod: "save", }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/vault/components/premium-badge.stories.ts b/apps/web/src/app/vault/components/premium-badge.stories.ts index 17622dbbd5f..331f72fd0ac 100644 --- a/apps/web/src/app/vault/components/premium-badge.stories.ts +++ b/apps/web/src/app/vault/components/premium-badge.stories.ts @@ -2,6 +2,7 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessageSender } from "@bitwarden/common/platform/messaging"; @@ -22,6 +23,14 @@ export default { moduleMetadata({ imports: [JslibModule, BadgeModule], providers: [ + { + provide: AccountService, + useValue: { + activeAccount$: of({ + id: "123", + }), + }, + }, { provide: I18nService, useFactory: () => { @@ -39,7 +48,7 @@ export default { { provide: BillingAccountProfileStateService, useValue: { - hasPremiumFromAnySource$: of(false), + hasPremiumFromAnySource$: () => of(false), }, }, ], diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 64118e47ee8..916c845e9d3 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -66,7 +66,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On protected messagingService: MessagingService, eventCollectionService: EventCollectionService, protected policyService: PolicyService, - organizationService: OrganizationService, + protected organizationService: OrganizationService, logService: LogService, passwordRepromptService: PasswordRepromptService, dialogService: DialogService, @@ -107,7 +107,11 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On // https://bitwarden.atlassian.net/browse/PM-10413 // cannot generate ssh keys so block creation - if (this.type === CipherType.SshKey && this.cipherId == null) { + if ( + this.type === CipherType.SshKey && + this.cipherId == null && + !(await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem)) + ) { this.type = CipherType.Login; this.cipher.type = CipherType.Login; } @@ -303,7 +307,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On this.cipher.type === CipherType.Login && this.cipher.login.totp && this.organization?.productTierType != ProductTierType.Free && - (this.cipher.organizationUseTotp || this.canAccessPremium) + ((this.canAccessPremium && this.cipher.organizationId == null) || + this.cipher.organizationUseTotp) ); } diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 88af1ef601b..bc639ff2f61 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -46,6 +46,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { dialogService, formBuilder, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions params?.folderId ? (this.folderId = params.folderId) : null; } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts index 7f7e0f075b7..88fae02275f 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts @@ -1,11 +1,14 @@ import { TestBed } from "@angular/core/testing"; -import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { + UserDecryptionOptions, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; @@ -22,18 +25,20 @@ describe("VaultBannersService", () => { let service: VaultBannersService; const isSelfHost = jest.fn().mockReturnValue(false); const hasPremiumFromAnySource$ = new BehaviorSubject(false); - const userId = "user-id" as UserId; + const userId = Utils.newGuid() as UserId; const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); const getEmailVerified = jest.fn().mockResolvedValue(true); - const hasMasterPassword = jest.fn().mockResolvedValue(true); - const getKdfConfig = jest - .fn() - .mockResolvedValue({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600000 }); - const getLastSync = jest.fn().mockResolvedValue(null); + const lastSync$ = new BehaviorSubject(null); + const userDecryptionOptions$ = new BehaviorSubject({ + hasMasterPassword: true, + }); + const kdfConfig$ = new BehaviorSubject({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600000 }); + const accounts$ = new BehaviorSubject>({ + [userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo, + }); beforeEach(() => { - jest.useFakeTimers(); - getLastSync.mockClear().mockResolvedValue(new Date("2024-05-14")); + lastSync$.next(new Date("2024-05-14")); isSelfHost.mockClear(); getEmailVerified.mockClear().mockResolvedValue(true); @@ -53,24 +58,26 @@ describe("VaultBannersService", () => { useValue: fakeStateProvider, }, { - provide: AccountService, - useValue: mockAccountServiceWith(userId), - }, - { - provide: TokenService, - useValue: { getEmailVerified }, + provide: PlatformUtilsService, + useValue: { isSelfHost }, }, { - provide: UserVerificationService, - useValue: { hasMasterPassword }, + provide: AccountService, + useValue: { accounts$ }, }, { provide: KdfConfigService, - useValue: { getKdfConfig }, + useValue: { getKdfConfig$: () => kdfConfig$ }, }, { provide: SyncService, - useValue: { getLastSync }, + useValue: { lastSync$: () => lastSync$ }, + }, + { + provide: UserDecryptionOptionsServiceAbstraction, + useValue: { + userDecryptionOptionsById$: () => userDecryptionOptions$, + }, }, ], }); @@ -82,39 +89,38 @@ describe("VaultBannersService", () => { describe("Premium", () => { it("waits until sync is completed before showing premium banner", async () => { - getLastSync.mockResolvedValue(new Date("2024-05-14")); hasPremiumFromAnySource$.next(false); isSelfHost.mockReturnValue(false); + lastSync$.next(null); service = TestBed.inject(VaultBannersService); - jest.advanceTimersByTime(201); + const premiumBanner$ = service.shouldShowPremiumBanner$(userId); + + // Should not emit when sync is null + await expect(firstValueFrom(premiumBanner$.pipe(take(1), timeout(100)))).rejects.toThrow(); - expect(await firstValueFrom(service.shouldShowPremiumBanner$)).toBe(true); + // Should emit when sync is completed + lastSync$.next(new Date("2024-05-14")); + expect(await firstValueFrom(premiumBanner$)).toBe(true); }); it("does not show a premium banner for self-hosted users", async () => { - getLastSync.mockResolvedValue(new Date("2024-05-14")); hasPremiumFromAnySource$.next(false); isSelfHost.mockReturnValue(true); service = TestBed.inject(VaultBannersService); - jest.advanceTimersByTime(201); - - expect(await firstValueFrom(service.shouldShowPremiumBanner$)).toBe(false); + expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false); }); it("does not show a premium banner when they have access to premium", async () => { - getLastSync.mockResolvedValue(new Date("2024-05-14")); hasPremiumFromAnySource$.next(true); isSelfHost.mockReturnValue(false); service = TestBed.inject(VaultBannersService); - jest.advanceTimersByTime(201); - - expect(await firstValueFrom(service.shouldShowPremiumBanner$)).toBe(false); + expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false); }); describe("dismissing", () => { @@ -125,7 +131,7 @@ describe("VaultBannersService", () => { jest.setSystemTime(date.getTime()); service = TestBed.inject(VaultBannersService); - await service.dismissBanner(VisibleVaultBanner.Premium); + await service.dismissBanner(userId, VisibleVaultBanner.Premium); }); afterEach(() => { @@ -134,7 +140,7 @@ describe("VaultBannersService", () => { it("updates state on first dismiss", async () => { const state = await firstValueFrom( - fakeStateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY).state$, + fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, ); const oneWeekLater = new Date("2023-06-15"); @@ -148,7 +154,7 @@ describe("VaultBannersService", () => { it("updates state on second dismiss", async () => { const state = await firstValueFrom( - fakeStateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY).state$, + fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, ); const oneMonthLater = new Date("2023-07-08"); @@ -162,7 +168,7 @@ describe("VaultBannersService", () => { it("updates state on third dismiss", async () => { const state = await firstValueFrom( - fakeStateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY).state$, + fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, ); const oneYearLater = new Date("2024-06-08"); @@ -178,40 +184,40 @@ describe("VaultBannersService", () => { describe("KDFSettings", () => { beforeEach(async () => { - hasMasterPassword.mockResolvedValue(true); - getKdfConfig.mockResolvedValue({ kdfType: KdfType.PBKDF2_SHA256, iterations: 599999 }); + userDecryptionOptions$.next({ hasMasterPassword: true }); + kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 599999 }); }); it("shows low KDF iteration banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(true); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); }); it("does not show low KDF iteration banner if KDF type is not PBKDF2_SHA256", async () => { - getKdfConfig.mockResolvedValue({ kdfType: KdfType.Argon2id, iterations: 600001 }); + kdfConfig$.next({ kdfType: KdfType.Argon2id, iterations: 600001 }); service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(false); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); }); it("does not show low KDF for iterations about 600,000", async () => { - getKdfConfig.mockResolvedValue({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600001 }); + kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600001 }); service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(false); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); }); it("dismisses low KDF iteration banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(true); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); - await service.dismissBanner(VisibleVaultBanner.KDFSettings); + await service.dismissBanner(userId, VisibleVaultBanner.KDFSettings); - expect(await service.shouldShowLowKDFBanner()).toBe(false); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); }); }); @@ -228,39 +234,44 @@ describe("VaultBannersService", () => { it("shows outdated browser banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowUpdateBrowserBanner()).toBe(true); + expect(await service.shouldShowUpdateBrowserBanner(userId)).toBe(true); }); it("dismisses outdated browser banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowUpdateBrowserBanner()).toBe(true); + expect(await service.shouldShowUpdateBrowserBanner(userId)).toBe(true); - await service.dismissBanner(VisibleVaultBanner.OutdatedBrowser); + await service.dismissBanner(userId, VisibleVaultBanner.OutdatedBrowser); - expect(await service.shouldShowUpdateBrowserBanner()).toBe(false); + expect(await service.shouldShowUpdateBrowserBanner(userId)).toBe(false); }); }); describe("VerifyEmail", () => { beforeEach(async () => { - getEmailVerified.mockResolvedValue(false); + accounts$.next({ + [userId]: { + ...accounts$.value[userId], + emailVerified: false, + }, + }); }); it("shows verify email banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowVerifyEmailBanner()).toBe(true); + expect(await service.shouldShowVerifyEmailBanner(userId)).toBe(true); }); it("dismisses verify email banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowVerifyEmailBanner()).toBe(true); + expect(await service.shouldShowVerifyEmailBanner(userId)).toBe(true); - await service.dismissBanner(VisibleVaultBanner.VerifyEmail); + await service.dismissBanner(userId, VisibleVaultBanner.VerifyEmail); - expect(await service.shouldShowVerifyEmailBanner()).toBe(false); + expect(await service.shouldShowVerifyEmailBanner(userId)).toBe(false); }); }); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index c18b046e35e..390b95fa2b1 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -1,28 +1,18 @@ import { Injectable } from "@angular/core"; -import { - Subject, - Observable, - combineLatest, - firstValueFrom, - map, - mergeMap, - take, - switchMap, - of, -} from "rxjs"; +import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateProvider, - ActiveUserState, PREMIUM_BANNER_DISK_LOCAL, BANNERS_DISMISSED_DISK, UserKeyDefinition, + SingleUserState, } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PBKDF2KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"; @@ -62,47 +52,25 @@ export const BANNERS_DISMISSED_DISK_KEY = new UserKeyDefinition; - - private premiumBannerState: ActiveUserState; - private sessionBannerState: ActiveUserState; - - /** - * Emits when the sync service has completed a sync - * - * This is needed because `hasPremiumFromAnySource$` will emit false until the sync is completed - * resulting in the premium banner being shown briefly on startup when the user has access to - * premium features. - */ - private syncCompleted$ = new Subject(); - constructor( - private tokenService: TokenService, - private userVerificationService: UserVerificationService, + private accountService: AccountService, private stateProvider: StateProvider, private billingAccountProfileStateService: BillingAccountProfileStateService, private platformUtilsService: PlatformUtilsService, private kdfConfigService: KdfConfigService, private syncService: SyncService, - private accountService: AccountService, - ) { - this.pollUntilSynced(); - this.premiumBannerState = this.stateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY); - this.sessionBannerState = this.stateProvider.getActive(BANNERS_DISMISSED_DISK_KEY); - - const premiumSources$ = this.accountService.activeAccount$.pipe( - take(1), - switchMap((account) => { - return combineLatest([ - account - ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) - : of(false), - this.premiumBannerState.state$, - ]); - }), - ); - - this.shouldShowPremiumBanner$ = this.syncCompleted$.pipe( + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + ) {} + + shouldShowPremiumBanner$(userId: UserId): Observable { + const premiumBannerState = this.premiumBannerState(userId); + const premiumSources$ = combineLatest([ + this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId), + premiumBannerState.state$, + ]); + + return this.syncService.lastSync$(userId).pipe( + filter((lastSync) => lastSync !== null), take(1), // Wait until the first sync is complete before considering the premium status mergeMap(() => premiumSources$), map(([canAccessPremium, dismissedState]) => { @@ -122,9 +90,9 @@ export class VaultBannersService { } /** Returns true when the update browser banner should be shown */ - async shouldShowUpdateBrowserBanner(): Promise { + async shouldShowUpdateBrowserBanner(userId: UserId): Promise { const outdatedBrowser = window.navigator.userAgent.indexOf("MSIE") !== -1; - const alreadyDismissed = (await this.getBannerDismissedState()).includes( + const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( VisibleVaultBanner.OutdatedBrowser, ); @@ -132,10 +100,12 @@ export class VaultBannersService { } /** Returns true when the verify email banner should be shown */ - async shouldShowVerifyEmailBanner(): Promise { - const needsVerification = !(await this.tokenService.getEmailVerified()); + async shouldShowVerifyEmailBanner(userId: UserId): Promise { + const needsVerification = !( + await firstValueFrom(this.accountService.accounts$.pipe(map((accounts) => accounts[userId]))) + )?.emailVerified; - const alreadyDismissed = (await this.getBannerDismissedState()).includes( + const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( VisibleVaultBanner.VerifyEmail, ); @@ -143,12 +113,14 @@ export class VaultBannersService { } /** Returns true when the low KDF iteration banner should be shown */ - async shouldShowLowKDFBanner(): Promise { - const hasLowKDF = (await this.userVerificationService.hasMasterPassword()) - ? await this.isLowKdfIteration() + async shouldShowLowKDFBanner(userId: UserId): Promise { + const hasLowKDF = ( + await firstValueFrom(this.userDecryptionOptionsService.userDecryptionOptionsById$(userId)) + )?.hasMasterPassword + ? await this.isLowKdfIteration(userId) : false; - const alreadyDismissed = (await this.getBannerDismissedState()).includes( + const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( VisibleVaultBanner.KDFSettings, ); @@ -156,11 +128,11 @@ export class VaultBannersService { } /** Dismiss the given banner and perform any respective side effects */ - async dismissBanner(banner: SessionBanners): Promise { + async dismissBanner(userId: UserId, banner: SessionBanners): Promise { if (banner === VisibleVaultBanner.Premium) { - await this.dismissPremiumBanner(); + await this.dismissPremiumBanner(userId); } else { - await this.sessionBannerState.update((current) => { + await this.sessionBannerState(userId).update((current) => { const bannersDismissed = current ?? []; return [...bannersDismissed, banner]; @@ -168,16 +140,32 @@ export class VaultBannersService { } } + /** + * + * @returns a SingleUserState for the premium banner reprompt state + */ + private premiumBannerState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY); + } + + /** + * + * @returns a SingleUserState for the session banners dismissed state + */ + private sessionBannerState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, BANNERS_DISMISSED_DISK_KEY); + } + /** Returns banners that have already been dismissed */ - private async getBannerDismissedState(): Promise { + private async getBannerDismissedState(userId: UserId): Promise { // `state$` can emit null when a value has not been set yet, // use nullish coalescing to default to an empty array - return (await firstValueFrom(this.sessionBannerState.state$)) ?? []; + return (await firstValueFrom(this.sessionBannerState(userId).state$)) ?? []; } /** Increment dismissal state of the premium banner */ - private async dismissPremiumBanner(): Promise { - await this.premiumBannerState.update((current) => { + private async dismissPremiumBanner(userId: UserId): Promise { + await this.premiumBannerState(userId).update((current) => { const numberOfDismissals = current?.numberOfDismissals ?? 0; const now = new Date(); @@ -213,22 +201,11 @@ export class VaultBannersService { }); } - private async isLowKdfIteration() { - const kdfConfig = await this.kdfConfigService.getKdfConfig(); + private async isLowKdfIteration(userId: UserId) { + const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId)); return ( kdfConfig.kdfType === KdfType.PBKDF2_SHA256 && kdfConfig.iterations < PBKDF2KdfConfig.ITERATIONS.defaultValue ); } - - /** Poll the `syncService` until a sync is completed */ - private pollUntilSynced() { - const interval = setInterval(async () => { - const lastSync = await this.syncService.getLastSync(); - if (lastSync !== null) { - clearInterval(interval); - this.syncCompleted$.next(); - } - }, 200); - } } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts index 5fdac63e932..f35a93f8b9c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts @@ -2,13 +2,17 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { RouterTestingModule } from "@angular/router/testing"; import { mock } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, Observable } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { BannerComponent, BannerModule } from "@bitwarden/components"; import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component"; @@ -21,21 +25,25 @@ describe("VaultBannersComponent", () => { let component: VaultBannersComponent; let fixture: ComponentFixture; const premiumBanner$ = new BehaviorSubject(false); + const mockUserId = Utils.newGuid() as UserId; const bannerService = mock({ - shouldShowPremiumBanner$: premiumBanner$, + shouldShowPremiumBanner$: jest.fn((userId$: Observable) => premiumBanner$), shouldShowUpdateBrowserBanner: jest.fn(), shouldShowVerifyEmailBanner: jest.fn(), shouldShowLowKDFBanner: jest.fn(), dismissBanner: jest.fn(), }); + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { - bannerService.shouldShowPremiumBanner$ = premiumBanner$; bannerService.shouldShowUpdateBrowserBanner.mockResolvedValue(false); bannerService.shouldShowVerifyEmailBanner.mockResolvedValue(false); bannerService.shouldShowLowKDFBanner.mockResolvedValue(false); + premiumBanner$.next(false); + await TestBed.configureTestingModule({ imports: [ BannerModule, @@ -62,6 +70,10 @@ describe("VaultBannersComponent", () => { provide: TokenService, useValue: mock(), }, + { + provide: AccountService, + useValue: accountService, + }, ], }) .overrideProvider(VaultBannersService, { useValue: bannerService }) @@ -135,7 +147,7 @@ describe("VaultBannersComponent", () => { dismissButton.dispatchEvent(new Event("click")); - expect(bannerService.dismissBanner).toHaveBeenCalledWith(banner); + expect(bannerService.dismissBanner).toHaveBeenCalledWith(mockUserId, banner); expect(component.visibleBanners).toEqual([]); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index 933b4899c94..161b2ccb7ef 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -2,8 +2,9 @@ // @ts-strict-ignore import { Component, Input, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { Observable } from "rxjs"; +import { firstValueFrom, map, Observable, switchMap } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { BannerModule } from "@bitwarden/components"; @@ -26,12 +27,17 @@ export class VaultBannersComponent implements OnInit { VisibleVaultBanner = VisibleVaultBanner; @Input() organizationsPaymentStatus: FreeTrial[] = []; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private vaultBannerService: VaultBannersService, private router: Router, private i18nService: I18nService, + private accountService: AccountService, ) { - this.premiumBannerVisible$ = this.vaultBannerService.shouldShowPremiumBanner$; + this.premiumBannerVisible$ = this.activeUserId$.pipe( + switchMap((userId) => this.vaultBannerService.shouldShowPremiumBanner$(userId)), + ); } async ngOnInit(): Promise { @@ -39,7 +45,8 @@ export class VaultBannersComponent implements OnInit { } async dismissBanner(banner: VisibleVaultBanner): Promise { - await this.vaultBannerService.dismissBanner(banner); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.vaultBannerService.dismissBanner(activeUserId, banner); await this.determineVisibleBanners(); } @@ -57,9 +64,12 @@ export class VaultBannersComponent implements OnInit { /** Determine which banners should be present */ private async determineVisibleBanners(): Promise { - const showBrowserOutdated = await this.vaultBannerService.shouldShowUpdateBrowserBanner(); - const showVerifyEmail = await this.vaultBannerService.shouldShowVerifyEmailBanner(); - const showLowKdf = await this.vaultBannerService.shouldShowLowKDFBanner(); + const activeUserId = await firstValueFrom(this.activeUserId$); + + const showBrowserOutdated = + await this.vaultBannerService.shouldShowUpdateBrowserBanner(activeUserId); + const showVerifyEmail = await this.vaultBannerService.shouldShowVerifyEmailBanner(activeUserId); + const showLowKdf = await this.vaultBannerService.shouldShowLowKDFBanner(activeUserId); this.visibleBanners = [ showBrowserOutdated ? VisibleVaultBanner.OutdatedBrowser : null, diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 5b68b3fbc2f..8c1d08b269c 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -779,16 +779,26 @@ export class VaultComponent implements OnInit, OnDestroy { null, cipherType, ); + const collectionId = + this.activeFilter.collectionId !== "AllCollections" && this.activeFilter.collectionId != null + ? this.activeFilter.collectionId + : null; + let organizationId = + this.activeFilter.organizationId !== "MyVault" && this.activeFilter.organizationId != null + ? this.activeFilter.organizationId + : null; + // Attempt to get the organization ID from the collection if present + if (collectionId) { + const organizationIdFromCollection = ( + await firstValueFrom(this.vaultFilterService.filteredCollections$) + ).find((c) => c.id === this.activeFilter.collectionId)?.organizationId; + if (organizationIdFromCollection) { + organizationId = organizationIdFromCollection; + } + } cipherFormConfig.initialValues = { - organizationId: - this.activeFilter.organizationId !== "MyVault" && this.activeFilter.organizationId != null - ? (this.activeFilter.organizationId as OrganizationId) - : null, - collectionIds: - this.activeFilter.collectionId !== "AllCollections" && - this.activeFilter.collectionId != null - ? [this.activeFilter.collectionId as CollectionId] - : [], + organizationId: organizationId as OrganizationId, + collectionIds: [collectionId as CollectionId], folderId: this.activeFilter.folderId, }; diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index ae07df524fe..14550968ba5 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -32,7 +32,6 @@ import { switchMap, takeUntil, tap, - withLatestFrom, } from "rxjs/operators"; import { @@ -194,6 +193,7 @@ export class VaultComponent implements OnInit, OnDestroy { protected currentSearchText$: Observable; protected freeTrial$: Observable; protected resellerWarning$: Observable; + protected prevCipherId: string | null = null; /** * A list of collections that the user can assign items to and edit those items within. * @protected @@ -538,25 +538,26 @@ export class VaultComponent implements OnInit, OnDestroy { firstSetup$ .pipe( - switchMap(() => this.route.queryParams), - // Only process the queryParams if the dialog is not open (only when extension refresh is enabled) + switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])), filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled), - withLatestFrom(allCipherMap$, allCollections$, organization$), switchMap(async ([qParams, allCiphersMap]) => { const cipherId = getCipherIdFromParams(qParams); + if (!cipherId) { + this.prevCipherId = null; + return; + } + + if (cipherId === this.prevCipherId) { return; } - const cipher = allCiphersMap[cipherId]; + this.prevCipherId = cipherId; + + const cipher = allCiphersMap[cipherId]; if (cipher) { let action = qParams.action; - // Default to "view" if extension refresh is enabled - if (action == null && this.extensionRefreshEnabled) { - action = "view"; - } - if (action == "showFailedToDecrypt") { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [cipherId as CipherId], @@ -569,6 +570,11 @@ export class VaultComponent implements OnInit, OnDestroy { return; } + // Default to "view" if extension refresh is enabled + if (action == null && this.extensionRefreshEnabled) { + action = "view"; + } + if (action === "view") { await this.viewCipherById(cipher); } else { @@ -1175,6 +1181,8 @@ export class VaultComponent implements OnInit, OnDestroy { // Navigate away if we deleted the collection we were viewing if (this.selectedCollection?.node.id === collection.id) { + // Clear the cipher cache to clear the deleted collection from the cipher state + await this.cipherService.clear(); void this.router.navigate([], { queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, queryParamsHandling: "merge", diff --git a/apps/web/src/connectors/captcha.ts b/apps/web/src/connectors/captcha.ts index 01a14a79a23..aad6eaa3d47 100644 --- a/apps/web/src/connectors/captcha.ts +++ b/apps/web/src/connectors/captcha.ts @@ -5,8 +5,12 @@ import { b64Decode, getQsParam } from "./common"; declare let hcaptcha: any; if (window.location.pathname.includes("mobile")) { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha-mobile.scss"); } else { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha.scss"); } @@ -50,6 +54,8 @@ async function start() { let decodedData: any; try { decodedData = JSON.parse(b64Decode(data, true)); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index 0f067b583b9..b5300ff65e7 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -3,6 +3,8 @@ import { getQsParam } from "./common"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./duo-redirect.scss"); const mobileDesktopCallback = "bitwarden://duo-callback"; diff --git a/apps/web/src/connectors/sso.ts b/apps/web/src/connectors/sso.ts index 3ec6a8f7a3d..b48c2b49d72 100644 --- a/apps/web/src/connectors/sso.ts +++ b/apps/web/src/connectors/sso.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { getQsParam } from "./common"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./sso.scss"); window.addEventListener("load", () => { diff --git a/apps/web/src/connectors/webauthn-fallback.ts b/apps/web/src/connectors/webauthn-fallback.ts index 971bc5e44a1..6f32bbaecf8 100644 --- a/apps/web/src/connectors/webauthn-fallback.ts +++ b/apps/web/src/connectors/webauthn-fallback.ts @@ -4,6 +4,8 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); let parsed = false; @@ -52,6 +54,8 @@ function parseParametersV2() { let dataObj: { data: any; btnText: string } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -103,6 +107,8 @@ function start() { let json: any; try { json = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/webauthn.ts b/apps/web/src/connectors/webauthn.ts index 14ba8a280ee..32e6e0ab673 100644 --- a/apps/web/src/connectors/webauthn.ts +++ b/apps/web/src/connectors/webauthn.ts @@ -3,6 +3,8 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); const mobileCallbackUri = "bitwarden://webauthn-callback"; @@ -88,6 +90,8 @@ function parseParametersV2() { } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -116,6 +120,8 @@ function start() { try { obj = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse webauthn data."); return; diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index f86b6ab497b..1dd2d62f2a5 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Teken asb. weer aan." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Teken asb. weer aan. Indien u ander Bitwarden-toepassings gebruik, teken daarop ook weer uit en aan." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Toestel" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Keur versoek goed" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Geen toestelversoeke" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index ed843a62cd1..2c2549e2f1e 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "بَدْء تسجيل الدخول" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "الرجاء تسجيل الدخول مرة أخرى." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "الجهاز" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "خطأ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index b8111a0e997..b140ae320be 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Kimliyinizi doğrulayın" }, + "whatIsADevice": { + "message": "Cihaz nədir?" + }, + "aDeviceIs": { + "message": "Cihaz, giriş etdiyiniz Bitwarden tətbiqinin unikal quraşdırmasıdır. Yenidən quraşdırılması, tətbiq datasının təmizlənməsi və ya çərəzlərin təmizlənməsi, cihazın bir neçə dəfə görünməsinə səbəb ola bilər." + }, "logInInitiated": { "message": "Giriş etmə başladıldı" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Lütfən yenidən giriş edin." }, + "currentSession": { + "message": "Hazırkı seans" + }, + "requestPending": { + "message": "Tələb gözlənir" + }, "logBackInOthersToo": { "message": "Lütfən yenidən giriş edin. Digər Bitwarden tətbiqlərini istifadə edirsinizsə, onlardan da çıxış edib təkrar giriş etməlisiniz." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 dəvət haqqınız var." + }, "userUsingTwoStep": { "message": "Bu istifadəçinin hesabını qorumaq üçün iki addımlı giriş istifadə edilir." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Giriş statusu" + }, + "firstLogin": { + "message": "İlk giriş" + }, + "trusted": { + "message": "Güvənli" + }, "creatingAccountOn": { "message": "Hesab yaradılır" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Xəta" }, + "decryptionError": { + "message": "Şifrə açma xətası" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Əlavə data itkisini önləmək üçün", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "müştəri dəstəyi ilə əlaqə saxlayın.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "İdarə edilən istifadəçilərə həm də \"Hesab geri qaytarılmasını idarə et\" icazəsi verilməlidir" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Tələbi təsdiqlə" }, + "deviceApproved": { + "message": "Cihaz təsdiqləndi" + }, + "deviceRemoved": { + "message": "Cihaz silindi" + }, + "removeDevice": { + "message": "Cihazı sil" + }, + "removeDeviceConfirmation": { + "message": "Bu cihazı silmək istədiyinizə əminsiniz?" + }, "noDeviceRequests": { "message": "Heç bir cihaz tələbi yoxdur" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Üzvləri çıxart" }, + "devices": { + "message": "Cihazlar" + }, + "deviceListDescription": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib. Tanımadığınız cihaz varsa, onu silin." + }, + "deviceListDescriptionTemp": { + "message": "Aşağıdakı cihazların hər birində hesabınıza giriş edilib." + }, "claimedDomains": { "message": "Götürülmüş domenlər" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Təşkilat abunəliyi yenidən başladıldı" + }, + "restartSubscription": { + "message": "Abunəliyinizi yenidən başladın" + }, + "suspendedManagedOrgMessage": { + "message": "Kömək üçün $PROVIDER$ ilə əlaqə saxlayın.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index ea705b38b4e..2a037c5b11c 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Ініцыяваны ўваход" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Калі ласка, увайдзіце паўторна." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Калі ласка, увайдзіце паўторна. Калі вы выкарыстоўваеце іншыя праграмы Bitwarden, выйдзіце з іх, а потым увайдзіце яшчэ раз." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Гэты карыстальнік выкарыстоўвае двухэтапны ўваход для абароны свайго ўліковага запісу." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Прылада" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Памылка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для кіравання карыстальнікамі неабходна даць дазвол на кіраванне аднаўленнем уліковым запісам" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Ухваліць запыт" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Няма запытаў ад прылады" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 1f5fb8d8c7e..531cfd18a42 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Потвърдете самоличността си" }, + "whatIsADevice": { + "message": "Какво представлява едно устройство?" + }, + "aDeviceIs": { + "message": "Едно устройство наричаме инсталацията на приложението Битуорден, където сте се вписали. Ако преинсталирате приложението, изчистите данните му или изтриете бисквитките си, това устройство може да се появи няколко пъти в списъка." + }, "logInInitiated": { "message": "Вписването е стартирано" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Впишете се отново." }, + "currentSession": { + "message": "Текуща сесия" + }, + "requestPending": { + "message": "Чакаща заявка" + }, "logBackInOthersToo": { "message": "Впишете се отново. Ако използвате и други приложения на Битуорден, впишете се отново и в тях." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Имате 1 оставаща покана." + }, "userUsingTwoStep": { "message": "Този потребител използва двустепенна защита за достъп." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Състояние на вписването" + }, + "firstLogin": { + "message": "Първо вписване" + }, + "trusted": { + "message": "Доверено" + }, "creatingAccountOn": { "message": "Създаване на регистрация в" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Грешка при дешифриране" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Битоурден не може да дешифрира елементите от трезора посочени по-долу." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Свържете се с поддръжката", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "за да избегнете загубата на данни.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Управлението на потребителите трябва да бъде дадено заедно с разрешението за Управление на възстановяването на регистрации" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Одобряване на заявката" }, + "deviceApproved": { + "message": "Устройството е одобрено" + }, + "deviceRemoved": { + "message": "Устройството е премахнато" + }, + "removeDevice": { + "message": "Премахване на устройството" + }, + "removeDeviceConfirmation": { + "message": "Наистина ли искате да премахнете това устройство?" + }, "noDeviceRequests": { "message": "Няма заявки от устройства" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Премахване на членовете" }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Вие сте се вписали във всяко от устройствата по-долу. Ако не разпознавате някое от тях, премахнете го сега." + }, + "deviceListDescriptionTemp": { + "message": "Вашият акаунт е вписан във всяко от устройствата по-долу." + }, "claimedDomains": { "message": "Присвоени домейни" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Абонаментът на организацията е рестартиран" + }, + "restartSubscription": { + "message": "Рестартирайте абонамента си" + }, + "suspendedManagedOrgMessage": { + "message": "Свържете се с $PROVIDER$ за помощ.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 56a04f3d870..b215e56e19a 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 3e9c4525b04..5f2605e71bc 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 1cf16dcc0fd..30649f3296d 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verificació de la vostra identitat" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "S'ha iniciat la sessió" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Torneu a iniciar sessió." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Torneu a iniciar la sessió. Si esteu utilitzant altres aplicacions Bitwarden, tanqueu-les i torneu-les a obrir també." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Aquest usuari fa servir l'inici de sessió en dues passes per protegir el seu compte." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositiu" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "L'administració d'usuaris també ha d'estar habilitada amb el permís de recuperació del compte de gestió" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprova la sol·licitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hi ha sol·licituds de dispositiu" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 64ce764de82..7c8978fac80 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Ověřte svou totožnost" }, + "whatIsADevice": { + "message": "Co je to zařízení?" + }, + "aDeviceIs": { + "message": "Zařízení je jedinečná instalace aplikace Bitwarden, ve které jste se přihlásili. Přeinstalování, vymazání dat aplikace nebo vymazání souborů cookie může vést k tomu, že se zařízení objeví vícekrát." + }, "logInInitiated": { "message": "Bylo zahájeno přihlášení" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Přihlaste se znovu." }, + "currentSession": { + "message": "Aktuální relace" + }, + "requestPending": { + "message": "Čekající požadavek" + }, "logBackInOthersToo": { "message": "Přihlaste se znovu. Používáte-li jiné aplikace Bitwardenu, přihlaste se znovu i v nich." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Zbývá Vám 1 pozvánka." + }, "userUsingTwoStep": { "message": "Tento uživatel používá pro ochranu svého účtu dvoufázové přihlášení." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Zařízení" }, + "loginStatus": { + "message": "Stav přihlášení" + }, + "firstLogin": { + "message": "První přihlášení" + }, + "trusted": { + "message": "Důvěryhodný" + }, "creatingAccountOn": { "message": "Vytváření účtu na" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrování" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nemohl dešifrovat níže uvedené položky v trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznickou podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "abyste zabránili ztrátě dat.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "S oprávněním spravovat obnovení hesla musí být povolena také správa uživatelů" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Schválit žádost" }, + "deviceApproved": { + "message": "Zařízení schváleno" + }, + "deviceRemoved": { + "message": "Zařízení odebráno" + }, + "removeDevice": { + "message": "Odebrat zařízení" + }, + "removeDeviceConfirmation": { + "message": "Opravdu chcete odebrat toto zařízení?" + }, "noDeviceRequests": { "message": "Žádné žádosti ze zařízení" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Odebrat členy" }, + "devices": { + "message": "Zařízení" + }, + "deviceListDescription": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení. Pokud zařízení nepoznáváte, odeberte jej nyní." + }, + "deviceListDescriptionTemp": { + "message": "Váš účet byl přihlášen do každého z níže uvedených zařízení." + }, "claimedDomains": { "message": "Uplatněné domény" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Předplatné organizace bylo restartováno" + }, + "restartSubscription": { + "message": "Restartovat Vaše předplatné" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktujte $PROVIDER$ pro pomoc.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index c0c39c91d30..ed8a03eb8b0 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index f71e1feeb3b..9731c344e6e 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Bekræft din identitet" }, + "whatIsADevice": { + "message": "Hvad er en enhed?" + }, + "aDeviceIs": { + "message": "En enhed er en unik installation af Bitwarden-appen, hvor man har logget ind. Geninstallation, rydning af app-data eller rydning af cookies kan medføre, at en enhed vises op flere gange." + }, "logInInitiated": { "message": "Indlogning påbegyndt" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Log ind igen." }, + "currentSession": { + "message": "Aktuel session" + }, + "requestPending": { + "message": "Anmodning afventer" + }, "logBackInOthersToo": { "message": "Log ind igen. Bruger du andre Bitwarden-applikationer, så log også ud og ind igen på disse." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Der er 1 invitation tilbage." + }, "userUsingTwoStep": { "message": "Denne bruger benytter totrins-login for at beskytte kontoen." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Enhed" }, + "loginStatus": { + "message": "Indlogningsstatus" + }, + "firstLogin": { + "message": "Første login" + }, + "trusted": { + "message": "Betroet" + }, "creatingAccountOn": { "message": "Opretter konto på" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fejl" }, + "decryptionError": { + "message": "Dekrypteringsfejl" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kunne ikke dekryptere boks-emne(r) anført nedenfor." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontakt kundeservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "for at undgå yderligere tab af data.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Håndtér brugere skal også tildeles med tilladelsen håndtér kontogendannelse" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Godkend anmodning" }, + "deviceApproved": { + "message": "Enhed godkendt" + }, + "deviceRemoved": { + "message": "Enhed fjernet" + }, + "removeDevice": { + "message": "Fjern enhed" + }, + "removeDeviceConfirmation": { + "message": "Sikker på, at denne enhed skal fjernes?" + }, "noDeviceRequests": { "message": "Ingen enhedsanmodninger" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Fjern medlemmer" }, + "devices": { + "message": "Enheder" + }, + "deviceListDescription": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor. Genkendes en enhed ikke, fjern den nu." + }, + "deviceListDescriptionTemp": { + "message": "Der er blevet logget ind på kontoen på hver af enhederne nedenfor." + }, "claimedDomains": { "message": "Registrerede domæner" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisationsabonnement genstartet" + }, + "restartSubscription": { + "message": "Genstart abonnementet" + }, + "suspendedManagedOrgMessage": { + "message": "Kontakt $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 93422471457..540db2dec05 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifiziere deine Identität" }, + "whatIsADevice": { + "message": "Was ist ein \"Gerät\"?" + }, + "aDeviceIs": { + "message": "Ein \"Gerät\" ist eine einzigartige Installation der Bitwarden-App, in der du dich angemeldet hast. Eine Neuinstallation, das Löschen von App-Daten oder das Löschen von Cookies könnte dazu führen, dass ein \"Gerät\" mehrfach erscheint." + }, "logInInitiated": { "message": "Anmeldung eingeleitet" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Bitte melden Sie sich erneut an." }, + "currentSession": { + "message": "Aktuelle Sitzung" + }, + "requestPending": { + "message": "Anfrage ausstehend" + }, "logBackInOthersToo": { "message": "Bitte melden Sie sich wieder an. Wenn Sie andere Bitwarden-Anwendungen verwenden, melden Sie sich auch dort ab und wieder neu an." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Du hast noch eine Einladung übrig." + }, "userUsingTwoStep": { "message": "Dieser Benutzer hat sein Konto mit einer Zwei-Faktor-Authentifizierung geschützt." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Gerät" }, + "loginStatus": { + "message": "Anmeldestatus" + }, + "firstLogin": { + "message": "Erste Anmeldung" + }, + "trusted": { + "message": "Vertrauenswürdig" + }, "creatingAccountOn": { "message": "Konto wird erstellt bei" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fehler" }, + "decryptionError": { + "message": "Entschlüsselungsfehler" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktiere das Kundenerfolgsteam", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "um zusätzlichen Datenverlust zu vermeiden.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Benutzer verwalten\" muss zusammen mit der \"Kontowiederherstellung verwalten\"-Berechtigung erteilt werden" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Anfrage genehmigen" }, + "deviceApproved": { + "message": "\"Gerät\" genehmigt" + }, + "deviceRemoved": { + "message": "\"Gerät\" entfernt" + }, + "removeDevice": { + "message": "\"Gerät\" entfernen" + }, + "removeDeviceConfirmation": { + "message": "Möchtest du das \"Gerät\" wirklich entfernen?" + }, "noDeviceRequests": { "message": "Keine Geräteanfragen" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Mitglieder entfernen" }, + "devices": { + "message": "\"Geräte\"" + }, + "deviceListDescription": { + "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt. Wenn du ein \"Gerät\" nicht wiedererkennst, dann entferne es jetzt." + }, + "deviceListDescriptionTemp": { + "message": "Dein Konto wurde bei jedem der folgenden \"Geräte\" eingeloggt." + }, "claimedDomains": { "message": "Beanspruchte Domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisations-Abonnement neu gestartet" + }, + "restartSubscription": { + "message": "Abonnement neu starten" + }, + "suspendedManagedOrgMessage": { + "message": "Kontaktiere $PROVIDER$ für Hilfe.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 42d24fae72d..adfb86665ab 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Επαληθεύστε την ταυτότητά σας" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Η σύνδεση ξεκίνησε" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Παρακαλούμε συνδεθείτε ξανά." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Παρακαλούμε συνδεθείτε ξανά. Εάν χρησιμοποιείτε άλλες εφαρμογές Bitwarden, αποσυνδεθείτε και επιστρέψτε σε αυτές επίσης." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Αυτός ο χρήστης χρησιμοποιεί τρόπο σύνδεσης δύο βημάτων για να προστατεύσει το λογαριασμό του." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Συσκευή" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Δημιουργία λογαριασμού στο" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Σφάλμα" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Έγκριση αιτήματος" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 1c7baa31756..eacba623ecd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -113,6 +113,60 @@ "atRiskMembers": { "message": "At-risk members" }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, @@ -3278,6 +3332,12 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, + "inviteZeroEmailDesc": { + "message": "You have 0 invites remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3786,6 +3846,67 @@ "trusted": { "message": "Trusted" }, + "needsApproval": { + "message": "Needs approval" + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -9992,6 +10113,9 @@ "deviceListDescription": { "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10077,8 +10201,8 @@ "organizationNameMaxLength": { "message": "Organization name cannot exceed 50 characters." }, - "resellerRenewalWarning": { - "message": "Your subscription will renew soon. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "resellerRenewalWarningMsg": { + "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10090,8 +10214,8 @@ } } }, - "resellerOpenInvoiceWarning": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "resellerOpenInvoiceWarningMgs": { + "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", "placeholders": { "reseller": { "content": "$1", @@ -10107,8 +10231,8 @@ } } }, - "resellerPastDueWarning": { - "message": "The invoice for your subscription has not been paid. To insure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "resellerPastDueWarningMsg": { + "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", "placeholders": { "reseller": { "content": "$1", diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index f37aa8150cf..a2002039ab9 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index ec67bb192c9..2564812802a 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications, log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognise a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisation subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 0170c225c03..21eba5a4a99 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Bonvolu saluti refoje." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Bonvolu saluti refoje. Se vi uzas aliajn programojn de Bitwarden, adiaŭu kaj ankaŭ salutu ilin." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ĉi tiu uzanto uzas du-paŝan ensaluton por protekti sian konton." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Aparato" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Eraro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 3c3d7bb135d..1393fe59b27 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifica tu identidad" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Inicio de sesión en proceso" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Por favor, vuelve a acceder." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, vuelve a acceder. Si estás utilizando otras aplicaciones de Bitwarden, cierra sesión y vuelva a acceder en ellas también." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Este usuario está usando autenticación de dos pasos para proteger su cuenta." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creando una cuenta en" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "El permiso \"gestionar usuarios\" también debe ser otorgado junto con el permiso \"gestionar cuenta\"" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprobar solicitud" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No hay solicitudes de dispositivo" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 700c748add8..9122e4b8778 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Kinnitage oma Identiteet" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Sisselogimine käivitatud" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Palun logi uuesti sisse." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Palun logi uuesti sisse. Kui kasutad teisi Bitwardeni rakendusi, pead ka nendes uuesti sisse logima." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Sellel kasutajal on kaheastmeline kinnitamine sisse lülitatud." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Seade" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Konto loomise asukoht" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Viga" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 4c7719ada13..576d8207aae 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Saioa hastea martxan da" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Mesedez, hasi saioa berriro." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mesedez, hasi saioa berriro. Bitwardenen beste aplikazioren bat erabiltzen ari bazara, itxi saioa eta hasi saioa berriro aplikazio horretan ere." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Erabiltzaile hau bi urratseko saio hasiera erabiltzen ari da bere kontua babesteko." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Gailua" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Akatsa" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index fe0ba063a7b..589a019973f 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ورود به سیستم آغاز شد" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "لطفاً دوباره وارد شوید." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "لطفاً دوباره وارد شوید. اگر از سایر برنامه‌های Bitwarden استفاده می‌کنید، از سیستم خارج شوید و دوباره به آن‌ها وارد شوید." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "این کاربر از ورود دو مرحله ای برای محافظت از حساب خود استفاده می‌کند." }, @@ -3765,6 +3780,15 @@ "device": { "message": "دستگاه" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "خطا" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "مدیر کاربران همچنین باید مجوز مدیریت بازیابی حساب کاربری را داشته باشد" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "درخواست تأیید" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "بدون درخواست دستگاه" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 234cfb0ee3e..b6825bed162 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Vahvista henkilöllisyytesi" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Kirjautuminen aloitettu" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Kirjaudu sisään uudelleen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Kirjaudu uudelleen sisään. Jos käytät muita Bitwarden-sovelluksia, kirjaudu myös niihin uudelleen." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Käyttäjä on suojannut tilinsä kaksivaiheisella kirjautumisella." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Laite" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Luodaan tili palvelimelle" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Virhe" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "\"Tilien palautusavun hallinta\" -oikeuden kanssa on myönnettävä myös \"Käyttäjien hallinta\" -oikeus." }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Hyväksy pyyntö" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Laitepyyntöjä ei ole" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Poista jäsenet" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index f5a9b078984..9dcacdb44f8 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Sinimulan ang pag-log in" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Mangyaring mag-log in ulit." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Mangyaring mag-log in ulit. Kung gumagamit ka ng iba pang mga aplikasyon pang-Bitwarden, mag-log out at log in rin ulit sa mga iyon." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Gumagamit ang user na ito ng dalawang hakbang na pag login upang maprotektahan ang kanilang account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Mali" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index e6c402293f8..933bfd8759f 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Vérifiez votre Identité" }, + "whatIsADevice": { + "message": "Qu'est-ce qu'un appareil ?" + }, + "aDeviceIs": { + "message": "Un appareil est une installation unique de l'application Bitwarden où vous vous êtes connecté. Réinstaller, effacer les données de l'application ou effacer vos cookies peut entraîner l'apparition d'un appareil plusieurs fois." + }, "logInInitiated": { "message": "Connexion initiée" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Veuillez vous reconnecter." }, + "currentSession": { + "message": "Session en cours" + }, + "requestPending": { + "message": "Demande en attente" + }, "logBackInOthersToo": { "message": "Veuillez vous reconnecter. Si vous utilisez d'autres applications Bitwarden, déconnectez-vous et reconnectez-vous également de celles-ci." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Cet utilisateur utilise l'authentification à deux facteurs pour protéger son compte." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Appareil" }, + "loginStatus": { + "message": "Statut de connexion" + }, + "firstLogin": { + "message": "Première connexion" + }, + "trusted": { + "message": "Approuvé" + }, "creatingAccountOn": { "message": "Création du compte sur" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Erreur" }, + "decryptionError": { + "message": "Erreur de déchiffrement" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden n'a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacter Customer Success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "pour éviter des pertes de données supplémentaires.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gérer les utilisateurs exige également d'avoir l'autorisation de gérer la restauration de compte" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approuver la demande" }, + "deviceApproved": { + "message": "Appareil approuvé" + }, + "deviceRemoved": { + "message": "Appareil supprimé" + }, + "removeDevice": { + "message": "Supprimer l'appareil" + }, + "removeDeviceConfirmation": { + "message": "Êtes-vous sûr de vouloir supprimer cet appareil ?" + }, "noDeviceRequests": { "message": "Aucune demande de l'appareil" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Retirer des membres" }, + "devices": { + "message": "Appareils" + }, + "deviceListDescription": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous. Si vous ne reconnaissez pas un appareil, supprimez-le maintenant." + }, + "deviceListDescriptionTemp": { + "message": "Votre compte a été connecté à chacun des appareils ci-dessous." + }, "claimedDomains": { "message": "Domaines réclamés" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "L'abonnement à l'organisation a été redémarré" + }, + "restartSubscription": { + "message": "Redémarrez votre abonnement" + }, + "suspendedManagedOrgMessage": { + "message": "Contactez $PROVIDER$ pour obtenir de l'aide.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 986350b0637..3e2c1161067 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 9c4ef530721..6db8255c46e 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "אנא התחבר שוב." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "אנא התחבר שוב. אם אתה משתמש באפליקציות נוספות של Bitwarden, סגור את החיבור והתחבר שוב גם באפליקציות הללו." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "משתמש זה הפעיל כניסה דו שלבית כדי להגן על חשבונו." }, @@ -3765,6 +3780,15 @@ "device": { "message": "מכשיר" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 82a0c12a390..25f7bc6e6f7 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 7b77ae9d58f..610b63d68c1 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Potvrdi svoj identitet" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Pokrenuta prijava" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Molimo, ponovno se prijavi." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Molimo, ponovno se prijavi. Ako koristiš druge aplikacije Bitwarden i u njima napravi odjavu/prijavu." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ovaj korisnik upotrebljava prijavu u dva koraka za zaštitu svog računa." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Uređaj" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Stvaranje računa na" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Greška" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Upravljanje korisnicima mora također biti uključeno s dozvolom za Upravljanje ponovnim postavljanjem lozinke" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Odobri zahtjev" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Nema zahtjeva na čekanju" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Ukloni članove" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Potvrđene domene" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index ecd7db04ca1..163355b20d9 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Személyazonosság ellenőrzése" }, + "whatIsADevice": { + "message": "Mi az eszköz?" + }, + "aDeviceIs": { + "message": "Az eszköz a Bitwarden alkalmazás egyedi telepítése, amelyre bejelentkeztünk. Az alkalmazás adatok újratelepítése, törlése vagy a sütik törlése azt eredményezheti, hogy egy eszköz többször is megjelenhet." + }, "logInInitiated": { "message": "A bejelentkezés elindításra került." }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Ismételten be kell jelentkezni." }, + "currentSession": { + "message": "Jelenlegi munkamenet" + }, + "requestPending": { + "message": "Függőben lévő kérelem" + }, "logBackInOthersToo": { "message": "Ismételten be kell jelentkezni. Ha másik Bitwarden alkalmazásokat használunk, ott is jelentkezzünk ki és ismételten be." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 meghívó maradt." + }, "userUsingTwoStep": { "message": "Ez a felhasználó kétlépcsős bejelentkezést használ fiókja védelmére." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Eszköz" }, + "loginStatus": { + "message": "Bejelentkezési állapot" + }, + "firstLogin": { + "message": "Első bejelentkezés" + }, + "trusted": { + "message": "Megbízható" + }, "creatingAccountOn": { "message": "Fiók létrehozása:" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Hiba" }, + "decryptionError": { + "message": "Visszafejtési hiba" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "A Bitwarden nem tudta visszafejteni az alább felsorolt ​​széf elemeket." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Ügyfélszolgálat elérése", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "további adatvesztés elkerülése érdekében.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A felhasználók kezelését engedélyezni kell a Jelszó visszaállításának kezelése jogosultsággal is." }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Kérés megerősítése" }, + "deviceApproved": { + "message": "Az eszköz jóváhagyásra került." + }, + "deviceRemoved": { + "message": "Az eszköz eltávolításra került." + }, + "removeDevice": { + "message": "Eszköz eltávolítása" + }, + "removeDeviceConfirmation": { + "message": "Biztos eltávolításra kerüljön ez az eszköz?" + }, "noDeviceRequests": { "message": "Nincs eszköz kérés" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Tagok eltávolítása" }, + "devices": { + "message": "Eszközök" + }, + "deviceListDescription": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett. Ha nem ismerünk fel egy eszközt, távolítsuk el most." + }, + "deviceListDescriptionTemp": { + "message": "A fiók az alábbi eszközök mindegyikére bejelentkezett." + }, "claimedDomains": { "message": "Igényelt tartományok" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "A szervezeti feliratkozás újraindult." + }, + "restartSubscription": { + "message": "Feliratkozás újra indítása" + }, + "suspendedManagedOrgMessage": { + "message": "Segítséget $PROVIDER$ szolgáltatótól kaphatunk.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 7517cac5ae5..3b861fb16da 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Harap masuk kembali." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Harap masuk kembali. Jika Anda menggunakan aplikasi Bitwarden lain, keluarlah dan masuk kembali ke sana juga." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Pengguna ini menggunakan proses masuk dua langkah untuk melindungi akun mereka." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Perangkat" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Galat" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 0154a3c8c78..8ce01a4737d 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifica la tua identità" }, + "whatIsADevice": { + "message": "Cos'è un dispositivo?" + }, + "aDeviceIs": { + "message": "Un dispositivo è un'installazione unica dell'app Bitwarden con la quale hai effettuato l'accesso. Reinstallazione, eliminazione dei dati dell'app o cancellazione dei cookie potrebbero causare la comparsa di un dispositivo più volte." + }, "logInInitiated": { "message": "Login avviato" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Accedi di nuovo." }, + "currentSession": { + "message": "Sessione attuale" + }, + "requestPending": { + "message": "Richiesta in attesa" + }, "logBackInOthersToo": { "message": "Accedi di nuovo. Se stai usando Bitwarden su altre app, esci e rientra anche in quelle." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Hai ancora 1 invito." + }, "userUsingTwoStep": { "message": "Questo utente usa la verifica in due passaggi per proteggere il suo account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Stato accesso" + }, + "firstLogin": { + "message": "Primo accesso" + }, + "trusted": { + "message": "Di fiducia" + }, "creatingAccountOn": { "message": "Creazione account su" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Errore" }, + "decryptionError": { + "message": "Errore di decifrazione" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden non può decifrare gli elementi elencati di seguito." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contatta il cliente correttamente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "per evitare ulteriori perdite di dati.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gestisci utenti deve essere abilitato con il permesso di gestire il ripristino delle password" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approva richiesta" }, + "deviceApproved": { + "message": "Dispositivo approvato" + }, + "deviceRemoved": { + "message": "Dispositivo rimosso" + }, + "removeDevice": { + "message": "Rimuovi dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Sei sicuro di voler rimuovere il dispositivo?" + }, "noDeviceRequests": { "message": "Nessuna richiesta da approvare" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Dispositivi" + }, + "deviceListDescription": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto. Se non riconosci un dispositivo, rimuovilo ora." + }, + "deviceListDescriptionTemp": { + "message": "Il tuo account è stato connesso a ciascuno dei dispositivi qui sotto." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Abbonamento organizzazione riavviato" + }, + "restartSubscription": { + "message": "Riavvia il tuo abbonamento" + }, + "suspendedManagedOrgMessage": { + "message": "Contatta $PROVIDER$ per assistenza.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 6b3b6f13b46..44213d143aa 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "本人確認" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ログイン開始" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "ログインし直してください" }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "ログインし直してください。他のBitwardenのアプリを使用している場合、同様にログインし直してください。" }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "このユーザーはアカウントを保護するため二段階認証を利用しています。" }, @@ -3765,6 +3780,15 @@ "device": { "message": "デバイス" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "アカウント作成:" }, @@ -5655,6 +5679,20 @@ "error": { "message": "エラー" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "ユーザーを管理するには、アカウントのリカバリ管理権限を付与する必要があります。" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "リクエストを承認" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "デバイスリクエストはありません" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "メンバーを削除" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index cc4dc222103..610bc0f6d87 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "ავტორიზაცია დაწყებულია" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index e1fd4cc9ac9..215bde966ad 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "ದಯವಿಟ್ಟು ಮತ್ತೆ ಲಾಗ್ ಇನ್ ಮಾಡಿ. ನೀವು ಇತರ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬಳಸುತ್ತಿದ್ದರೆ ಲಾಗ್ಔಟ್ ಮಾಡಿ ಮತ್ತು ಅವುಗಳಿಗೆ ಹಿಂತಿರುಗಿ." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "ಈ ಬಳಕೆದಾರರು ತಮ್ಮ ಖಾತೆಯನ್ನು ರಕ್ಷಿಸಲು ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಬಳಸುತ್ತಿದ್ದಾರೆ." }, @@ -3765,6 +3780,15 @@ "device": { "message": "ಡಿವೈಸ್" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "ದೋಷ" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 7966191f530..0cd1302ea2f 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "다시 로그인해 주세요." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "다시 로그인해 주세요. 다른 Bitwarden 앱을 사용 중인 경우 해당 앱에서도 다시 로그인해야 합니다." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "이 사용자는 계정을 보호하기 위해 2단계 로그인을 사용하고 있습니다." }, @@ -3765,6 +3780,15 @@ "device": { "message": "기기" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "오류" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 0b830e6dc9a..51ca6a0f798 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Jāapliecina sava identitāte" }, + "whatIsADevice": { + "message": "Kas ir ierīce?" + }, + "aDeviceIs": { + "message": "Ierīce ir atsevišķa uzstādīta Bitwarde lietotne, kurā ir veikta pieteikšanās. Atkārtota uzstādīšana, lietotnes datu vai sīkdatņu notīrīšana var beigties ar ierīces vairākkārtīgu parādīšanos." + }, "logInInitiated": { "message": "Uzsākta pieteikšanās" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Lūgums pieteikties atkārtoti." }, + "currentSession": { + "message": "Pašreizējā sesija" + }, + "requestPending": { + "message": "Pieprasījums ir apstrādē" + }, "logBackInOthersToo": { "message": "Lūgums pieteikties atkārtoti. Ja tiek izmantotas citas Bitwarden lietotnes, ir nepieciešams atteikties un atkārtoti pieteikties arī tajās." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Tev ir atlicis 1 uzaicinājums." + }, "userUsingTwoStep": { "message": "Šis lietotājs izmanto divpakāpju pieteikšanos, lai aizsargātu savu kontu." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Ierīce" }, + "loginStatus": { + "message": "Pieteikšanās stāvoklis" + }, + "firstLogin": { + "message": "Pirmā pieteikšanās" + }, + "trusted": { + "message": "Uzticama" + }, "creatingAccountOn": { "message": "Tiek veidots konts" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Kļūda" }, + "decryptionError": { + "message": "Atšifrēšanas kļūda" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nevarēja atšifrēt zemāk uzskaitītos glabātavas vienumus." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Jāsazinās ar klientu atbalstu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "lai izvairītos no papildu datu zaudējumiem.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Lietotāju pārvaldīšanai ir jābūt iespējotai arī ar konta atkopšanas pārvaldīšanas atļauju" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Apstiprināt pieprasījumu" }, + "deviceApproved": { + "message": "Ierīce apstiprināta" + }, + "deviceRemoved": { + "message": "Ierīce noņemta" + }, + "removeDevice": { + "message": "Noņemt ierīci" + }, + "removeDeviceConfirmation": { + "message": "Vai tiešām noņemt šo ierīci?" + }, "noDeviceRequests": { "message": "Nav ierīču pieprasījumu" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Noņemt dalībniekus" }, + "devices": { + "message": "Ierīces" + }, + "deviceListDescription": { + "message": "Kontā ir notikusi pieteikšanās katrā no zemāk uzskaitītajām ierīcēm. Ja kāda no tām nav atpazīstama, tā ir uzreiz jānoņem." + }, + "deviceListDescriptionTemp": { + "message": "Ar kontu ir veikta pieteikšanās katrā no zemāk uzskaitītajām ierīcēm." + }, "claimedDomains": { "message": "Pieteiktie domēni" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Apvienības abonements atsākts" + }, + "restartSubscription": { + "message": "Atsākt savu abonementu" + }, + "suspendedManagedOrgMessage": { + "message": "Jāsazinās ar $PROVIDER$, lai iegūtu palīdzību.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index c6f9aa8d853..a6eb0e474fd 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "ദയവായി തിരികെ പ്രവേശിക്കുക." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "ഈ ഉപയോക്താവ് അവരുടെ അക്കൗണ്ട് രണ്ട്-പ്രവേശനം ഉപയോഗിച്ച് സുരക്ഷിതമാക്കിയിരിക്കുന്നു." }, @@ -3765,6 +3780,15 @@ "device": { "message": "ഉപകരണം" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index af26fc4df94..da80546c9a5 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Pålogging startet" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Vennligst logg på igjen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vennligst logg inn på nytt. Dersom du bruker andre Bitwarden-applikasjoner logg av og på på dem også." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Denne brukeren bruker 2-trinnsinnlogging til å beskytte kontoen sin." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Feil" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index e51fb3ced67..7ebca5938e0 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index cd72a94251c..b38e6f4c3c3 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Controleer je identiteit" }, + "whatIsADevice": { + "message": "Wat is een apparaat?" + }, + "aDeviceIs": { + "message": "Een apparaat is een unieke installatie van de Bitwarden-app waar je bent ingelogd. Het opnieuw installeren, verwijderen van app-gegevens of het wissen van uw cookies kan zorgen voor het meerdere keren weergeven van dat apparaat." + }, "logInInitiated": { "message": "Inloggen gestart" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Log opnieuw in." }, + "currentSession": { + "message": "Huidige sessie" + }, + "requestPending": { + "message": "Verzoek in behandeling" + }, "logBackInOthersToo": { "message": "Svp opnieuw inloggen. Als je andere Bitwarden-applicaties gebruikt, dan moet je daar ook uit- en inloggen." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Je hebt 1 uitnodiging over." + }, "userUsingTwoStep": { "message": "Het account van deze gebruiker is beschermd met tweestapsaanmelding." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Apparaat" }, + "loginStatus": { + "message": "Loginstatus" + }, + "firstLogin": { + "message": "Eerst inloggen" + }, + "trusted": { + "message": "Vertrouwd" + }, "creatingAccountOn": { "message": "Account maken bij" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fout" }, + "decryptionError": { + "message": "Ontsleutelingsfout" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden kon de onderstaande kluisitem(s) niet ontsleutelen." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Neem contact op met de klantenservice", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "om extra dataverlies te voorkomen.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gebruikers beheren moet je ook accountherstel beheren toekennen" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aanvraag goedkeuren" }, + "deviceApproved": { + "message": "Apparaat goedgekeurd" + }, + "deviceRemoved": { + "message": "Apparaat verwijderd" + }, + "removeDevice": { + "message": "Apparaat verwijderen" + }, + "removeDeviceConfirmation": { + "message": "Weet je zeker dat je dit apparaat wilt verwijderen?" + }, "noDeviceRequests": { "message": "Geen apparaatverzoeken" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Leden verwijderen" }, + "devices": { + "message": "Apparaten" + }, + "deviceListDescription": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld. Als je een apparaat niet herkent, verwijder het nu." + }, + "deviceListDescriptionTemp": { + "message": "Je account is op elk van de onderstaande apparaten aangemeld." + }, "claimedDomains": { "message": "Geverifieerde domeinen" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organisatieabonnement hervat" + }, + "restartSubscription": { + "message": "Abonnement hervatten" + }, + "suspendedManagedOrgMessage": { + "message": "Neem contact op met $PROVIDER$ voor hulp.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 0e3c134ba4d..cb6d070485d 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Eining" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 58e4de95317..d063fa818a7 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Zweryfikuj swoją tożsamość" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Logowanie rozpoczęte" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Zaloguj się ponownie." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Zaloguj się ponownie. Jeśli używasz innych aplikacji Bitwarden, wyloguj się i zaloguj ponownie również w nich." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Ten użytkownik korzysta z logowania dwustopniowego, aby chronić swoje konto." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Urządzenie" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Tworzenie konta na" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Błąd" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Zarządzanie użytkownikami musi być również przyznane z uprawnieniem do zarządzania odzyskiwaniem kont" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Zatwierdź prośbę" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Brak urządzeń do zatwierdzenia" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 463b7f5e060..b911d5b273a 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifique sua identidade" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Login iniciado" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Por favor, reinicie a sessão." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Por favor, reinicie a sessão. Se estiver usando outros aplicativos do Bitwarden, encerre a sessão e reinicie também." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Este usuário está usando o login em duas etapas para proteger a sua conta." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Criando conta em" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Gerenciar usuários também devem ser concedidos com a permissão de gerenciar a recuperação de contas" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprovar solicitação" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Nenhum pedido de dispositivo" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remover membro?" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 57a2eee7cf5..637c65f644d 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verifique a sua identidade" }, + "whatIsADevice": { + "message": "O que é um dispositivo?" + }, + "aDeviceIs": { + "message": "Um dispositivo é uma instalação única da app Bitwarden onde o utilizador iniciou sessão. Reinstalar, limpar os dados da aplicação ou limpar os seus cookies pode fazer com que um dispositivo apareça várias vezes." + }, "logInInitiated": { "message": "A preparar o início de sessão" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Por favor, inicie sessão novamente." }, + "currentSession": { + "message": "Sessão atual" + }, + "requestPending": { + "message": "Pedido pendente" + }, "logBackInOthersToo": { "message": "Por favor, inicie sessão novamente. Se estiver a utilizar outras aplicações Bitwarden, termine a sessão e volte a iniciar sessão nessas também." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ainda tem 1 convite." + }, "userUsingTwoStep": { "message": "Este utilizador está a utilizar a verificação de dois passos para proteger a sua conta." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispositivo" }, + "loginStatus": { + "message": "Estado do início de sessão" + }, + "firstLogin": { + "message": "Primeiro início de sessão" + }, + "trusted": { + "message": "Confiável" + }, "creatingAccountOn": { "message": "A criar conta em" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Erro" }, + "decryptionError": { + "message": "Erro de desencriptação" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "O Bitwarden não conseguiu desencriptar o(s) item(ns) do cofre listado(s) abaixo." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contacte o serviço de apoio ao cliente", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "para evitar perdas adicionais de dados.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "A permissão para gerir utilizadores deve também ser concedida com a permissão para gerir a recuperação da conta" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Aprovar pedido" }, + "deviceApproved": { + "message": "Dispositivo aprovado" + }, + "deviceRemoved": { + "message": "Dispositivo removido" + }, + "removeDevice": { + "message": "Remover dispositivo" + }, + "removeDeviceConfirmation": { + "message": "Tem a certeza de que pretende remover este dispositivo?" + }, "noDeviceRequests": { "message": "Sem pedidos de dispositivos" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remover membros" }, + "devices": { + "message": "Dispositivos" + }, + "deviceListDescription": { + "message": "A sua conta foi iniciada em cada um dos dispositivos abaixo. Se não reconhecer um dispositivo, remova-o agora." + }, + "deviceListDescriptionTemp": { + "message": "Tem sessão iniciada em cada um dos dispositivos abaixo." + }, "claimedDomains": { "message": "Domínios reivindicados" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Subscrição da organização reiniciada" + }, + "restartSubscription": { + "message": "Reinicie a sua subscrição" + }, + "suspendedManagedOrgMessage": { + "message": "Contacte a $PROVIDER$ para obter assistência.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 18ff0b2158f..42d13688444 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Autentificare inițiată" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Vă rugăm să vă conectați din nou." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vă rugăm să vă reconectați. Dacă utilizați și alte aplicații Bitwarden, reconectați-vă la ele de asemenea." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Acest utilizator folosește conectarea în două etape pentru a-și proteja contul." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Dispozitiv" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Eroare" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 89209f2fa52..28386190d17 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Подтвердите вашу личность" }, + "whatIsADevice": { + "message": "Что такое устройство?" + }, + "aDeviceIs": { + "message": "Устройство - это уникальная установка приложения Bitwarden, в которой вы авторизовались. Переустановка, очистка данных приложения или очистка файлов cookie может привести к тому, что устройство будет отображаться несколько раз." + }, "logInInitiated": { "message": "Вход инициирован" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Пожалуйста, войдите снова." }, + "currentSession": { + "message": "Текущая сессия" + }, + "requestPending": { + "message": "Запрос в ожидании" + }, "logBackInOthersToo": { "message": "Пожалуйста, войдите снова. Если вы используете другие приложения Bitwarden, выполните на них выход и повторный вход." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "У вас осталось 1 приглашение." + }, "userUsingTwoStep": { "message": "Этот пользователь использует двухэтапную аутентификацию для защиты своего аккаунта." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Устройство" }, + "loginStatus": { + "message": "Статус входа" + }, + "firstLogin": { + "message": "Первый вход" + }, + "trusted": { + "message": "Доверенный" + }, "creatingAccountOn": { "message": "Создание аккаунта" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Ошибка" }, + "decryptionError": { + "message": "Ошибка расшифровки" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden не удалось расшифровать элемент(ы) хранилища, перечисленные ниже." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Обратитесь в службу поддержки,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "чтобы избежать дополнительной потери данных.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Для управления пользователями также необходимо предоставить разрешение на управление восстановлением аккаунта" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Одобрить запрос" }, + "deviceApproved": { + "message": "Устройство одобрено" + }, + "deviceRemoved": { + "message": "Устройство удалено" + }, + "removeDevice": { + "message": "Удалить устройство" + }, + "removeDeviceConfirmation": { + "message": "Вы уверены, что хотите удалить это устройство?" + }, "noDeviceRequests": { "message": "Нет запросов устройств" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Удалить участников" }, + "devices": { + "message": "Устройства" + }, + "deviceListDescription": { + "message": "Ваш аккаунт был авторизован на каждом из перечисленных ниже устройств. Если устройство вам не знакомо, удалите его сейчас." + }, + "deviceListDescriptionTemp": { + "message": "Ваш аккаунт авторизован на перечисленных ниже устройствах." + }, "claimedDomains": { "message": "Зарегистрированные домены" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Подписка организации перезапущена" + }, + "restartSubscription": { + "message": "Перезапустить подписку" + }, + "suspendedManagedOrgMessage": { + "message": "Обратитесь за помощью к $PROVIDER$.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 0649f83f519..df5803c0fd9 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index dbffa25048c..d8adb9116ec 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Overte svoju totožnosť" }, + "whatIsADevice": { + "message": "Čo je zariadenie?" + }, + "aDeviceIs": { + "message": "Zariadenie je jednotlivá inštalácia aplikácie Bitwarden, do ktorej ste sa prihlásili. Preinštalovanie, vymazanie údajov aplikácie alebo vymazanie súborov cookie môže mať za následok, že sa zariadenie objaví viackrát." + }, "logInInitiated": { "message": "Iniciované prihlásenie" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Prosím, opäť sa prihláste." }, + "currentSession": { + "message": "Aktuálna relácia" + }, + "requestPending": { + "message": "Žiadosť čaká na spracovanie" + }, "logBackInOthersToo": { "message": "Prosím odhláste sa. Ak používate iné Bitwarden aplikácie, odhláste sa a opäť sa prihláste aj v nich." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "Ostáva vám 1 pozvánka." + }, "userUsingTwoStep": { "message": "Tento používateľ používa dvojstupňové overovanie aby si zabezpečil konto." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Zariadenie" }, + "loginStatus": { + "message": "Stav prihlásenia" + }, + "firstLogin": { + "message": "Prvé prihlásenie" + }, + "trusted": { + "message": "Dôveryhodné" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Chyba" }, + "decryptionError": { + "message": "Chyba dešifrovania" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden nedokázal dešifrovať nižšie uvedené položky trezoru." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Kontaktujte zákaznícku podporu,", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "aby ste predišli ďalším stratám údajov.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Schváliť žiadosť" }, + "deviceApproved": { + "message": "Zariadenie schválené" + }, + "deviceRemoved": { + "message": "Zariadenie odstránené" + }, + "removeDevice": { + "message": "Odstrániť zariadenie" + }, + "removeDeviceConfirmation": { + "message": "Ste si istí, že chcete odstrániť toto zariadenie?" + }, "noDeviceRequests": { "message": "Žiadne žiadosti pre zariadenia" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Odstrániť členov" }, + "devices": { + "message": "Zariadenia" + }, + "deviceListDescription": { + "message": "Vaše konto bolo prihlásené do každého z nižšie uvedených zariadení. Ak niektoré zariadenie nepoznáte, teraz ho odstráňte." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Privlastnené domény" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 9a9535aebaa..75ad87494f0 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Prijava se je začela" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Prosimo, ponovno se prijavite." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Prosimo, ponovno se prijavite. Če uporabljate druge Bitwarden aplikacije, se odjavite in ponovno prijavite tudi tam." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index e15d6d66653..0f3853331c2 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Потврдите идентитет" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Пријава је покренута" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Молимо да се поново пријавите." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Молимо вас да се поново пријавите. Ако користите друге Bitwarden апликације, одјавите се и вратите се и на њих." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Овај корисник користи пријаву у два корака за заштиту свог налога." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Уређај" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Креирај налог на" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Грешка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Корисницима за управљање такође мора бити додељена дозвола за опоравак налога за управљање" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Одобри захтев" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Нема захтева уређаја" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Уклони чланове" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index b2cd3a877d4..06572d08fea 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 14d7bc4572c..f9737643631 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Inloggning påbörjad" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Vänligen logga in igen." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vänligen logga in igen. Om du använder andra Bitwarden-applikationer, logga ut och in igen i dem också." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Denna användare använder tvåstegsverifiering för att skydda sitt konto." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Enhet" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Skapa konto på" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Fel" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Inga enhetsförfrågningar" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 36ab0050700..e6cce3405d5 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index c731b9ff87e..5f5818581f6 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Verify your Identity" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Please log back in." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Please log back in. If you are using other Bitwarden applications log out and back in to those as well." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Device" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index ba53ebe7dd4..97c0d00dec2 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Kimliğinizi doğrulayın" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Giriş başlatıldı" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Lütfen yeniden giriş yapın." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Lütfen yeniden oturum açın. Diğer Bitwarden uygulamalarını kullanıyorsanız onlarda da oturumunuzu kapatıp yeniden açın." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "1 davetiyeniz kaldı." + }, "userUsingTwoStep": { "message": "Bu kullanıcı hesabını korumak için iki aşamalı giriş kullanıyor." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Cihaz" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Hesap oluşturuluyor:" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Hata" }, + "decryptionError": { + "message": "Şifre çözme sorunu" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "İsteği onayla" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Cihaz isteği yok" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Hesabınıza aşağıdaki cihazlardan giriş yapıldı." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index c17befc27ce..d8cd9270763 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Підтвердьте свою особу" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Ініційовано вхід" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Повторно виконайте вхід." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Будь ласка, повторно виконайте вхід. Якщо ви користуєтесь іншими програмами Bitwarden, також вийдіть із них, і знову увійдіть." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "Цей користувач використовує двоетапну перевірку для захисту свого облікового запису." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Пристрій" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Створення облікового запису" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Помилка" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Разом із дозволом на керування відновленням облікового запису також необхідно надати дозвіл на керування користувачами" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Схвалити запит" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "Немає запитів з пристрою" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Вилучити учасників" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Заявлені домени" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 9f4156014c6..ff9f789f6f4 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "Xác minh danh tính của bạn" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "Log in initiated" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "Hãy đăng nhập lại." }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "Vui lòng đăng nhập lại. Nếu bạn đang dùng những ứng dụng Bitwarden khác, vui lòng đăng xuất and đăng nhập lại những ứng dụng đó." }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "This user is using two-step login to protect their account." }, @@ -3765,6 +3780,15 @@ "device": { "message": "Thiết bị" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "Creating account on" }, @@ -5655,6 +5679,20 @@ "error": { "message": "Error" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "Manage users must also be granted with the manage account recovery permission" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "Approve request" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "No device requests" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f293adcf62a..ae0b3744ec6 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -172,7 +172,7 @@ "message": "登录凭据" }, "personalDetails": { - "message": "个人信息" + "message": "个人详细信息" }, "identification": { "message": "身份" @@ -181,10 +181,10 @@ "message": "联系信息" }, "cardDetails": { - "message": "支付卡详情" + "message": "支付卡详细信息" }, "cardBrandDetails": { - "message": "$BRAND$ 详情", + "message": "$BRAND$ 详细信息", "placeholders": { "brand": { "content": "$1", @@ -684,7 +684,7 @@ "message": "项目" }, "itemDetails": { - "message": "项目详情" + "message": "项目详细信息" }, "itemName": { "message": "项目名称" @@ -958,13 +958,13 @@ "message": "您的登录会话已过期。" }, "restartRegistration": { - "message": "重新开始注册" + "message": "重启注册" }, "expiredLink": { "message": "失效链接" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "请重新注册或尝试登录。" + "message": "请重启注册或尝试登录。" }, "youMayAlreadyHaveAnAccount": { "message": "您可能已经有一个账户了" @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "验证您的身份" }, + "whatIsADevice": { + "message": "什么是设备?" + }, + "aDeviceIs": { + "message": "设备是您登录过的 Bitwarden App 的独立安装。重新安装、清除 App 数据或清除 Cookie,可能会导致设备多次出现。" + }, "logInInitiated": { "message": "登录已发起" }, @@ -1707,7 +1713,7 @@ } }, "loggedOutWarning": { - "message": "接下来将会注销您当前的会话,要求您重新登录。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "emailChanged": { "message": "电子邮箱已保存" @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "请重新登录。" }, + "currentSession": { + "message": "当前会话" + }, + "requestPending": { + "message": "请求待处理" + }, "logBackInOthersToo": { "message": "请重新登录。如果您还在使用其他 Bitwarden 应用程序,也请注销并重新登陆。" }, @@ -1795,7 +1807,7 @@ "message": "您是否担心自己的账户在其他设备上登录过?请按照以下步骤取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" }, "deauthorizeSessionsWarning": { - "message": "接下来将会注销您当前的会话,并要求您重新登录。如果有设置两步登录,也需要重新认证。其他设备上的活动会话可能会继续保持最多一小时。" + "message": "继续操作还将使您退出当前会话,并要求您重新登录。如果有设置两步登录,也需要重新验证。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "sessionsDeauthorized": { "message": "已取消所有会话授权" @@ -2141,7 +2153,7 @@ "message": "前往 bitwarden.com 吗?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。在 bitwarden.com 网站上了解更多信息。" + "message": "Bitwarden 验证器允许您存储验证器密钥以及为两步验证流程生成 TOTP 代码。访问 bitwarden.com 网站了解更多信息。" }, "twoStepAuthenticatorScanCodeV2": { "message": "用您的验证器 App 扫描下面的二维码或输入密钥。" @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "您还剩下 1 个邀请。" + }, "userUsingTwoStep": { "message": "此用户正在使用两步登录来保护他们的账户。" }, @@ -3765,6 +3780,15 @@ "device": { "message": "设备" }, + "loginStatus": { + "message": "登录状态" + }, + "firstLogin": { + "message": "首次登录" + }, + "trusted": { + "message": "信任" + }, "creatingAccountOn": { "message": "创建账户至" }, @@ -4819,7 +4843,7 @@ "message": "先决条件" }, "requireSsoPolicyReq": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "requireSsoPolicyReqError": { "message": "单一组织策略未启用。" @@ -5545,7 +5569,7 @@ "message": "对于具有主密码的现有账户,需要成员自行注册后,管理员才可以恢复他们的账户。对于新的成员,自动注册后将打开账户恢复功能。" }, "accountRecoverySingleOrgRequirementDesc": { - "message": "激活此策略前,需先开启「单一组织」企业策略。" + "message": "必须先开启「单一组织」企业策略,然后才能激活此策略。" }, "resetPasswordPolicyAutoEnroll": { "message": "自动注册" @@ -5655,6 +5679,20 @@ "error": { "message": "错误" }, + "decryptionError": { + "message": "解密错误" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden 无法解密下列密码库项目。" + }, + "contactCSToAvoidDataLossPart1": { + "message": "联系客户成功团队", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "以避免额外的数据丢失。", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "授予「管理账户恢复」权限时,必须同时授予「管理用户」权限" }, @@ -5793,10 +5831,10 @@ "message": "更新主密码" }, "updateMasterPasswordWarning": { - "message": "您的主密码最近被您组织的管理员更改过。要访问此密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码最近被您组织的管理员更改过。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "masterPasswordInvalidWarning": { - "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" + "message": "您的主密码不符合此组织的策略要求。要加入此组织,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -6546,7 +6584,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个字符生成强大的密码。", + "message": " 使用 $RECOMMENDED$ 个或更多字符生成强大的密码。", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6556,7 +6594,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " 使用 $RECOMMENDED$ 或更多个单词生成强大的密码短语。", + "message": " 使用 $RECOMMENDED$ 个或更多单词生成强大的密码短语。", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "批准请求" }, + "deviceApproved": { + "message": "设备已批准" + }, + "deviceRemoved": { + "message": "设备已移除" + }, + "removeDevice": { + "message": "移除设备" + }, + "removeDeviceConfirmation": { + "message": "确定要移除此设备吗?" + }, "noDeviceRequests": { "message": "无设备请求" }, @@ -8975,7 +9025,7 @@ } }, "deleteProviderWarningDescription": { - "message": "删除 $ID$ 之前,您必须取消链接所有的客户。", + "message": "您必须取消链接所有的客户,然后才能删除 $ID$。", "placeholders": { "id": { "content": "$1", @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "移除成员" }, + "devices": { + "message": "设备" + }, + "deviceListDescription": { + "message": "您的账户在以下设备上登录过。如果您无法识别某个设备,请立即将其移除。" + }, + "deviceListDescriptionTemp": { + "message": "您的账户在以下设备上登录过。" + }, "claimedDomains": { "message": "已声明的域名" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "组织订阅已重启" + }, + "restartSubscription": { + "message": "重启您的订阅" + }, + "suspendedManagedOrgMessage": { + "message": "联系 $PROVIDER$ 获取协助。", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index ae60ea3cdb7..002d0d55e0f 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1128,6 +1128,12 @@ "verifyIdentity": { "message": "核實你的身份" }, + "whatIsADevice": { + "message": "What is a device?" + }, + "aDeviceIs": { + "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + }, "logInInitiated": { "message": "登入已發起" }, @@ -1715,6 +1721,12 @@ "logBackIn": { "message": "請重新登入。" }, + "currentSession": { + "message": "Current session" + }, + "requestPending": { + "message": "Request pending" + }, "logBackInOthersToo": { "message": "請重新登入。若您還在使用其他 Bitwarden 應用程式,也請登出後再重新登入。" }, @@ -3266,6 +3278,9 @@ } } }, + "inviteSingleEmailDesc": { + "message": "You have 1 invite remaining." + }, "userUsingTwoStep": { "message": "此使用者正在使用兩步驟登入保護帳戶。" }, @@ -3765,6 +3780,15 @@ "device": { "message": "裝置" }, + "loginStatus": { + "message": "Login status" + }, + "firstLogin": { + "message": "First login" + }, + "trusted": { + "message": "Trusted" + }, "creatingAccountOn": { "message": "建立帳號於" }, @@ -5655,6 +5679,20 @@ "error": { "message": "錯誤" }, + "decryptionError": { + "message": "Decryption error" + }, + "couldNotDecryptVaultItemsBelow": { + "message": "Bitwarden could not decrypt the vault item(s) listed below." + }, + "contactCSToAvoidDataLossPart1": { + "message": "Contact customer success", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, + "contactCSToAvoidDataLossPart2": { + "message": "to avoid additional data loss.", + "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" + }, "accountRecoveryManageUsers": { "message": "管理使用者也必須被授予管理帳戶復原的權限" }, @@ -8236,6 +8274,18 @@ "approveRequest": { "message": "核准要求" }, + "deviceApproved": { + "message": "Device approved" + }, + "deviceRemoved": { + "message": "Device removed" + }, + "removeDevice": { + "message": "Remove device" + }, + "removeDeviceConfirmation": { + "message": "Are you sure you want to remove this device?" + }, "noDeviceRequests": { "message": "沒有裝置要求" }, @@ -9935,6 +9985,15 @@ "removeMembers": { "message": "Remove members" }, + "devices": { + "message": "Devices" + }, + "deviceListDescription": { + "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + }, + "deviceListDescriptionTemp": { + "message": "Your account was logged in to each of the devices below." + }, "claimedDomains": { "message": "Claimed domains" }, @@ -10062,5 +10121,20 @@ "example": "02/14/2024" } } + }, + "restartOrganizationSubscription": { + "message": "Organization subscription restarted" + }, + "restartSubscription": { + "message": "Restart your subscription" + }, + "suspendedManagedOrgMessage": { + "message": "Contact $PROVIDER$ for assistance.", + "placeholders": { + "provider": { + "content": "$1", + "example": "Acme c" + } + } } } diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index 3ae0778250c..2c0108ca3e2 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -5,6 +5,7 @@ config.content = [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", + "../../libs/key-management/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../bitwarden_license/bit-web/src/**/*.{html,ts}", diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts index dc6a6d8e906..1c51f9397c5 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -1,10 +1,10 @@ import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class ApproveCommand { diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts index f5dff801f27..767acea99f7 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts @@ -2,12 +2,12 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { MessageResponse } from "@bitwarden/cli/models/response/message.response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyAllCommand { diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts index 58c1c576433..87e633b2bee 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -1,10 +1,10 @@ import { firstValueFrom } from "rxjs"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; import { Response } from "@bitwarden/cli/models/response"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationAuthRequestService } from "../../../../bit-common/src/admin-console/auth-requests"; import { ServiceContainer } from "../../service-container"; export class DenyCommand { diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index b8d5852088a..94dad65fdc9 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -90,3 +90,22 @@ export type MemberDetailsFlat = { email: string; cipherId: string; }; + +/** + * Member email with the number of at risk passwords + * At risk member detail that contains the email + * and the count of at risk ciphers + */ +export type AtRiskMemberDetail = { + email: string; + atRiskPasswordCount: number; +}; + +/* + * A list of applications and the count of + * at risk passwords for each application + */ +export type AtRiskApplicationDetail = { + applicationName: string; + atRiskPasswordCount: number; +}; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts index d0530dfd821..c3bcc59eca5 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts @@ -12,6 +12,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ApplicationHealthReportDetail, ApplicationHealthReportSummary, + AtRiskMemberDetail, + AtRiskApplicationDetail, CipherHealthReportDetail, CipherHealthReportUriDetail, ExposedPasswordDetail, @@ -89,6 +91,54 @@ export class RiskInsightsReportService { return results$; } + /** + * Generates a list of members with at-risk passwords along with the number of at-risk passwords. + */ + generateAtRiskMemberList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskMemberDetail[] { + const memberRiskMap = new Map(); + + cipherHealthReportDetails.forEach((app) => { + app.atRiskMemberDetails.forEach((member) => { + if (memberRiskMap.has(member.email)) { + memberRiskMap.set(member.email, memberRiskMap.get(member.email) + 1); + } else { + memberRiskMap.set(member.email, 1); + } + }); + }); + + return Array.from(memberRiskMap.entries()).map(([email, atRiskPasswordCount]) => ({ + email, + atRiskPasswordCount, + })); + } + + generateAtRiskApplicationList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskApplicationDetail[] { + const appsRiskMap = new Map(); + + cipherHealthReportDetails + .filter((app) => app.atRiskPasswordCount > 0) + .forEach((app) => { + if (appsRiskMap.has(app.applicationName)) { + appsRiskMap.set( + app.applicationName, + appsRiskMap.get(app.applicationName) + app.atRiskPasswordCount, + ); + } else { + appsRiskMap.set(app.applicationName, app.atRiskPasswordCount); + } + }); + + return Array.from(appsRiskMap.entries()).map(([applicationName, atRiskPasswordCount]) => ({ + applicationName, + atRiskPasswordCount, + })); + } + /** * Gets the summary from the application health report. Returns total members and applications as well * as the total at risk members and at risk applications diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index a0a44f2ab30..7791840950b 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../libs/shared/tsconfig.libs", + "extends": "../../libs/shared/tsconfig", "include": ["src", "spec"], "exclude": ["node_modules", "dist"], "compilerOptions": { diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index 760877ff8bc..ac8ad3112b9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -98,6 +98,8 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy { title: null, message: this.i18nService.t("loginRequestApproved"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.toastService.showToast({ variant: "error", diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts index 2a2ae73227a..1cbe57a7082 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts @@ -16,7 +16,7 @@ import { import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; import { OrgDomainServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction"; import { OrganizationDomainResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain.response"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { HttpStatusCode } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -54,7 +54,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { private validationService: ValidationService, private toastService: ToastService, private configService: ConfigService, - private policyApiService: PolicyApiServiceAbstraction, + private policyService: PolicyService, ) { this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.AccountDeprovisioning, @@ -83,9 +83,14 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { await this.orgDomainApiService.getAllByOrgId(this.organizationId); if (await this.configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning)) { - const singleOrgPolicy = await this.policyApiService.getPolicy( - this.organizationId, - PolicyType.SingleOrg, + const singleOrgPolicy = await firstValueFrom( + this.policyService.policies$.pipe( + map((policies) => + policies.find( + (p) => p.type === PolicyType.SingleOrg && p.organizationId === this.organizationId, + ), + ), + ), ); this.singleOrgPolicyEnabled = singleOrgPolicy?.enabled ?? false; } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index f773db6c11c..ecf649b8f31 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -23,8 +23,7 @@ import { KeyService } from "@bitwarden/key-management"; templateUrl: "setup.component.html", }) export class SetupComponent implements OnInit, OnDestroy { - @ViewChild(ManageTaxInformationComponent) - manageTaxInformationComponent: ManageTaxInformationComponent; + @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent; loading = true; providerId: string; @@ -111,7 +110,7 @@ export class SetupComponent implements OnInit, OnDestroy { try { this.formGroup.markAllAsTouched(); - if (!this.manageTaxInformationComponent.validate() || !this.formGroup.valid) { + if (!this.taxInformationComponent.validate() || !this.formGroup.valid) { return; } @@ -125,7 +124,7 @@ export class SetupComponent implements OnInit, OnDestroy { request.key = key; request.taxInfo = new ExpandedTaxInfoUpdateRequest(); - const taxInformation = this.manageTaxInformationComponent.getTaxInformation(); + const taxInformation = this.taxInformationComponent.getTaxInformation(); request.taxInfo.country = taxInformation.country; request.taxInfo.postalCode = taxInformation.postalCode; @@ -147,6 +146,7 @@ export class SetupComponent implements OnInit, OnDestroy { await this.router.navigate(["/providers", provider.id]); } catch (e) { + e.message = this.i18nService.translate(e.message) || e.message; this.validationService.showError(e); } }; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts index b9c09a0d671..94d9ab2d8ee 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.module.ts @@ -1,8 +1,8 @@ import { NgModule } from "@angular/core"; import { BannerModule } from "@bitwarden/components"; +import { OnboardingModule } from "@bitwarden/web-vault/app/shared/components/onboarding/onboarding.module"; -import { OnboardingModule } from "../../../../../../apps/web/src/app/shared/components/onboarding/onboarding.module"; import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { OverviewRoutingModule } from "./overview-routing.module"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 631f6409748..78d367776d4 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -8,8 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +import { RouterService } from "@bitwarden/web-vault/app/core"; -import { RouterService } from "../../../../../../../apps/web/src/app/core/router.service"; import { ProjectView } from "../../models/view/project.view"; import { ProjectService } from "../project.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts index ebd00a83ea5..a4079a6def3 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -8,8 +8,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; +import { RouterService } from "@bitwarden/web-vault/app/core"; -import { RouterService } from "../../../../../../../../clients/apps/web/src/app/core/router.service"; import { ServiceAccountView } from "../../models/view/service-account.view"; import { ServiceAccountService } from "../service-account.service"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts index 9ae9b2acec3..c5934067fd7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts @@ -107,6 +107,8 @@ export class SecretsManagerPortingApiService { return secret; }), ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html index ea1a4f9db31..e17ac078687 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html @@ -27,17 +27,19 @@

{{ "allApplications" | i18n }}

@@ -82,7 +84,7 @@

{{ "allApplications" | i18n }}

(change)="onCheckboxChange(r.id, $event)" /> - + {{ r.applicationName }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index f4d3656071d..5fb12fed090 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -19,6 +19,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { + DialogService, Icons, NoItemsModule, SearchModule, @@ -30,6 +31,9 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; +import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component"; +import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component"; +import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @Component({ @@ -99,6 +103,7 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { protected dataService: RiskInsightsDataService, protected organizationService: OrganizationService, protected reportService: RiskInsightsReportService, + protected dialogService: DialogService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -135,6 +140,27 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { return item.applicationName; } + showAppAtRiskMembers = async (applicationName: string) => { + openAppAtRiskMembersDialog(this.dialogService, { + members: + this.dataSource.data.find((app) => app.applicationName === applicationName) + ?.atRiskMemberDetails ?? [], + applicationName, + }); + }; + + showOrgAtRiskMembers = async () => { + this.dialogService.open(OrgAtRiskMembersDialogComponent, { + data: this.reportService.generateAtRiskMemberList(this.dataSource.data), + }); + }; + + showOrgAtRiskApps = async () => { + this.dialogService.open(OrgAtRiskAppsDialogComponent, { + data: this.reportService.generateAtRiskApplicationList(this.dataSource.data), + }); + }; + onCheckboxChange(id: number, event: Event) { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html new file mode 100644 index 00000000000..383a1eccabe --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html @@ -0,0 +1,21 @@ + + {{ applicationName }} + +
+ {{ "atRiskMembersWithCount" | i18n: members.length }} + {{ + "atRiskMembersDescriptionWithApp" | i18n: applicationName + }} +
+ +
{{ member.email }}
+
+
+
+
+ + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts new file mode 100644 index 00000000000..d6a757fe897 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts @@ -0,0 +1,35 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { MemberDetailsFlat } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; + +type AppAtRiskMembersDialogParams = { + members: MemberDetailsFlat[]; + applicationName: string; +}; + +export const openAppAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AppAtRiskMembersDialogParams, +) => + dialogService.open(AppAtRiskMembersDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./app-at-risk-members-dialog.component.html", + imports: [ButtonModule, CommonModule, JslibModule, DialogModule], +}) +export class AppAtRiskMembersDialogComponent { + protected members: MemberDetailsFlat[]; + protected applicationName: string; + + constructor(@Inject(DIALOG_DATA) private params: AppAtRiskMembersDialogParams) { + this.members = params.members; + this.applicationName = params.applicationName; + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html new file mode 100644 index 00000000000..298011b2157 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html @@ -0,0 +1,25 @@ + + + {{ "atRiskApplicationsWithCount" | i18n: atRiskApps.length }} + + +
+ {{ "atRiskApplicationsDescription" | i18n }} +
+
{{ "application" | i18n }}
+
{{ "atRiskPasswords" | i18n }}
+
+ +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
+
+
+ + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts new file mode 100644 index 00000000000..0ae00f60874 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts @@ -0,0 +1,24 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AtRiskApplicationDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; + +export const openOrgAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AtRiskApplicationDetail[], +) => + dialogService.open(OrgAtRiskAppsDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./org-at-risk-apps-dialog.component.html", + imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], +}) +export class OrgAtRiskAppsDialogComponent { + constructor(@Inject(DIALOG_DATA) protected atRiskApps: AtRiskApplicationDetail[]) {} +} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html new file mode 100644 index 00000000000..41ac8af7886 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html @@ -0,0 +1,27 @@ + + + {{ "atRiskMembersWithCount" | i18n: atRiskMembers.length }} + + +
+ {{ + "atRiskMembersDescription" | i18n + }} +
+
{{ "email" | i18n }}
+
{{ "atRiskPasswords" | i18n }}
+
+ +
+
{{ member.email }}
+
{{ member.atRiskPasswordCount }}
+
+
+
+
+ + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts new file mode 100644 index 00000000000..72518843d94 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts @@ -0,0 +1,24 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AtRiskMemberDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; + +export const openOrgAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AtRiskMemberDetail[], +) => + dialogService.open(OrgAtRiskMembersDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./org-at-risk-members-dialog.component.html", + imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], +}) +export class OrgAtRiskMembersDialogComponent { + constructor(@Inject(DIALOG_DATA) protected atRiskMembers: AtRiskMemberDetail[]) {} +} diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts index 6aaaf1a4066..321aae165c5 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -103,7 +103,7 @@ export class MemberAccessReportComponent implements OnInit { usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: MemberDialogTab.Role, - numConfirmedMembers: this.dataSource.data.length, + numSeatsUsed: this.dataSource.data.length, }, }); diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts index 9919da8d08e..cf2f3b6417b 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessExportItem } from "../view/member-access-export.view"; import { MemberAccessReportView } from "../view/member-access-report.view"; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts index 6aab54f77d5..7d6beca48ec 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { OrganizationId } from "@bitwarden/common/src/types/guid"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { MemberAccessReportApiService } from "./member-access-report-api.service"; import { memberAccessReportsMock } from "./member-access-report.mock"; diff --git a/libs/admin-console/jest.config.js b/libs/admin-console/jest.config.js index f2a8e6458af..5131753964c 100644 --- a/libs/admin-console/jest.config.js +++ b/libs/admin-console/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts index 4aa54429aad..048a4733948 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts @@ -91,7 +91,7 @@ describe("DefaultvNextCollectionService", () => { // Assert emitted values expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: "DEC_NAME_" + collection1.id, @@ -167,7 +167,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -205,7 +205,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("UPDATED_ENC_NAME_" + collection1.id), @@ -230,7 +230,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -253,7 +253,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: newCollection3.id, name: makeEncString("ENC_NAME_" + newCollection3.id), diff --git a/libs/admin-console/test.setup.ts b/libs/admin-console/test.setup.ts index 6be6e7b8dd1..8ab102f2cf4 100644 --- a/libs/admin-console/test.setup.ts +++ b/libs/admin-console/test.setup.ts @@ -1,6 +1,10 @@ import { webcrypto } from "crypto"; + +import { addCustomMatchers } from "@bitwarden/common/spec"; import "jest-preset-angular/setup-jest"; +addCustomMatchers(); + Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { value: () => { diff --git a/libs/admin-console/tsconfig.json b/libs/admin-console/tsconfig.json index 6004a56fb55..4f057fd6af0 100644 --- a/libs/admin-console/tsconfig.json +++ b/libs/admin-console/tsconfig.json @@ -1,5 +1,13 @@ { - "extends": "../shared/tsconfig.libs", - "include": ["src", "spec"], + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/key-management": ["../key-management/src"] + } + }, + "include": ["src", "spec", "../../libs/common/custom-matchers.d.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/angular/jest.config.js b/libs/angular/jest.config.js index c8e748575c0..5e73614eb8e 100644 --- a/libs/angular/jest.config.js +++ b/libs/angular/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/angular/src/auth/components/login-v1.component.ts b/libs/angular/src/auth/components/login-v1.component.ts index 0775114bd8c..ffe1dda3aed 100644 --- a/libs/angular/src/auth/components/login-v1.component.ts +++ b/libs/angular/src/auth/components/login-v1.component.ts @@ -397,6 +397,8 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni email, deviceIdentifier, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.showLoginWithDevice = false; } diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index 18bfe546600..e2b41ad086d 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -157,7 +157,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), - message: error, + message: this.i18nService.t("webauthnCancelOrTimeout"), }); }, (info: string) => { diff --git a/libs/angular/src/directives/copy-click.directive.ts b/libs/angular/src/directives/copy-click.directive.ts index cb346ebf8d3..ece867c09fd 100644 --- a/libs/angular/src/directives/copy-click.directive.ts +++ b/libs/angular/src/directives/copy-click.directive.ts @@ -4,8 +4,7 @@ import { Directive, HostListener, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { ToastVariant } from "@bitwarden/components/src/toast/toast.component"; +import { ToastService, ToastVariant } from "@bitwarden/components"; @Directive({ selector: "[appCopyClick]", diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index f5940b8e144..803808612cf 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -20,6 +20,7 @@ import { DefaultLoginComponentService, LoginDecryptionOptionsService, DefaultLoginDecryptionOptionsService, + DefaultLoginApprovalComponentService, } from "@bitwarden/auth/angular"; import { AuthRequestServiceAbstraction, @@ -39,6 +40,7 @@ import { DefaultAuthRequestApiService, DefaultLoginSuccessHandlerService, LoginSuccessHandlerService, + LoginApprovalComponentServiceAbstraction, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; @@ -301,6 +303,8 @@ import { IndividualVaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NewDeviceVerificationNoticeService } from "../../../vault/src/services/new-device-verification-notice.service"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service"; import { ViewCacheService } from "../platform/abstractions/view-cache.service"; @@ -1403,6 +1407,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultAuthRequestApiService, deps: [ApiServiceAbstraction, LogService], }), + safeProvider({ + provide: LoginApprovalComponentServiceAbstraction, + useClass: DefaultLoginApprovalComponentService, + deps: [], + }), safeProvider({ provide: LoginDecryptionOptionsService, useClass: DefaultLoginDecryptionOptionsService, diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 8d286e0a3f9..bf2e68b71cd 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -128,7 +128,7 @@ export class AddEditComponent implements OnInit, OnDestroy { protected policyService: PolicyService, protected logService: LogService, protected passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService, + protected organizationService: OrganizationService, protected dialogService: DialogService, protected win: Window, protected datePipe: DatePipe, diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 425b4be2840..1a4c428aae2 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -192,6 +192,8 @@ export class AttachmentsComponent implements OnInit { title: null, message: this.i18nService.t("fileSavedToDevice"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } @@ -285,6 +287,8 @@ export class AttachmentsComponent implements OnInit { this.i18nService.t("attachmentSaved"), ); this.onReuploadedAttachment.emit(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/libs/angular/src/vault/components/icon.component.html b/libs/angular/src/vault/components/icon.component.html index 976c6ea421d..f16545617c9 100644 --- a/libs/angular/src/vault/components/icon.component.html +++ b/libs/angular/src/vault/components/icon.component.html @@ -4,13 +4,13 @@ [src]="data.image" [appFallbackSrc]="data.fallbackImage" *ngIf="data.imageEnabled && data.image" - class="tw-max-h-6 tw-max-w-6 tw-rounded-md" + class="tw-h-6 tw-w-6 tw-rounded-md" alt="" decoding="async" loading="lazy" /> diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index fc12aeff2f2..ef9aff736ed 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -65,6 +65,7 @@ export class ViewComponent implements OnDestroy, OnInit { showPrivateKey: boolean; canAccessPremium: boolean; showPremiumRequiredTotp: boolean; + showUpgradeRequiredTotp: boolean; totpCode: string; totpCodeFormatted: string; totpDash: number; @@ -151,22 +152,25 @@ export class ViewComponent implements OnDestroy, OnInit { this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); this.showPremiumRequiredTotp = - this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp; + this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationId; this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ this.collectionId as CollectionId, ]); + this.showUpgradeRequiredTotp = + this.cipher.login.totp && this.cipher.organizationId && !this.cipher.organizationUseTotp; + if (this.cipher.folderId) { this.folder = await ( await firstValueFrom(this.folderService.folderViews$(activeUserId)) ).find((f) => f.id == this.cipher.folderId); } - if ( - this.cipher.type === CipherType.Login && - this.cipher.login.totp && - (cipher.organizationUseTotp || this.canAccessPremium) - ) { + const canGenerateTotp = this.cipher.organizationId + ? this.cipher.organizationUseTotp + : this.canAccessPremium; + + if (this.cipher.type === CipherType.Login && this.cipher.login.totp && canGenerateTotp) { await this.totpUpdateCode(); const interval = this.totpService.getTimeInterval(this.cipher.login.totp); await this.totpTick(interval); @@ -462,6 +466,8 @@ export class ViewComponent implements OnDestroy, OnInit { fileName: attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts index e278113a653..ba19cf808ee 100644 --- a/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.spec.ts @@ -9,6 +9,8 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; import { VaultProfileService } from "../services/vault-profile.service"; diff --git a/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts index 20550e0e8cf..8b406877a12 100644 --- a/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts +++ b/libs/angular/src/vault/guards/new-device-verification-notice.guard.ts @@ -9,6 +9,8 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { NewDeviceVerificationNoticeService } from "../../../../vault/src/services/new-device-verification-notice.service"; import { VaultProfileService } from "../services/vault-profile.service"; diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index dd0b49f356a..260780e1964 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -22,6 +22,8 @@ import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "../../abstractions/deprecated-vault-filter.service"; import { DynamicTreeNode } from "../models/dynamic-tree-node.model"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { COLLAPSED_GROUPINGS } from "./../../../../../common/src/vault/services/key-state/collapsed-groupings.state"; const NestingDelimiter = "/"; diff --git a/libs/angular/tsconfig.json b/libs/angular/tsconfig.json index 6004a56fb55..6c510f81492 100644 --- a/libs/angular/tsconfig.json +++ b/libs/angular/tsconfig.json @@ -1,5 +1,25 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/generator-components": ["../tools/generator/components/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/importer/core": ["../importer/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault": ["../vault/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/auth/jest.config.js b/libs/auth/jest.config.js index 8bc834c7dab..121d423be17 100644 --- a/libs/auth/jest.config.js +++ b/libs/auth/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index b07504b7c8d..9f504c75d29 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -18,7 +18,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { LockIcon } from "../icons"; import { RegistrationCheckEmailIcon } from "../icons/registration-check-email.icon"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts index 91229f38ab2..05ddb9614f1 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -9,8 +9,14 @@ import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IconModule, Icon } from "../../../../components/src/icon"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { TypographyModule } from "../../../../components/src/typography"; import { BitwardenLogo, BitwardenShield } from "../icons"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts index 27eb27c53b9..c7e15d9dcfa 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts @@ -7,7 +7,11 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "../../../../components/src/button"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "../../../../components/src/utils/i18n-mock.service"; import { LockIcon } from "../icons"; diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index 94baecb9ef2..c613cf5f533 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -26,7 +26,11 @@ import { } from "@bitwarden/components"; import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InputsFieldMatch } from "../../../../angular/src/auth/validators/inputs-field-match.validator"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SharedModule } from "../../../../components/src/shared"; import { PasswordCalloutComponent } from "../password-callout/password-callout.component"; diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 99c0aba81b8..41577328f87 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -14,7 +14,8 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests"; import { InputPasswordComponent } from "./input-password.component"; diff --git a/libs/auth/src/angular/login-approval/login-approval.component.html b/libs/auth/src/angular/login-approval/login-approval.component.html index ddbc48d71a3..c0cb9b9caf4 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.html +++ b/libs/auth/src/angular/login-approval/login-approval.component.html @@ -1,23 +1,31 @@ {{ "areYouTryingtoLogin" | i18n }} -

{{ "logInAttemptBy" | i18n: email }}

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

{{ fingerprintPhrase }}

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

{{ authRequestResponse?.requestDeviceType }}

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

{{ authRequestResponse?.requestIpAddress }}

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

{{ requestTimeText }}

-
+ +
+ +
+
+ + +

{{ "logInAttemptBy" | i18n: email }}

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

{{ fingerprintPhrase }}

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

{{ authRequestResponse?.requestDeviceType }}

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

{{ authRequestResponse?.requestIpAddress }}

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

{{ requestTimeText }}

+
+

type="button" buttonType="primary" [bitAction]="approveLogin" - [bitDialogClose]="true" + [disabled]="loading" > {{ "confirmLogIn" | i18n }} @@ -34,7 +42,7 @@

{{ "logInAttemptBy" | i18n: email }}

type="button" buttonType="secondary" [bitAction]="denyLogin" - [bitDialogClose]="true" + [disabled]="loading" > {{ "denyLogIn" | i18n }} diff --git a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts index ff598bdeb91..da30df62fff 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts @@ -13,6 +13,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -29,6 +30,7 @@ describe("LoginApprovalComponent", () => { let i18nService: MockProxy; let dialogRef: MockProxy; let toastService: MockProxy; + let validationService: MockProxy; const testNotificationId = "test-notification-id"; const testEmail = "test@bitwarden.com"; @@ -41,6 +43,7 @@ describe("LoginApprovalComponent", () => { i18nService = mock(); dialogRef = mock(); toastService = mock(); + validationService = mock(); accountService.activeAccount$ = of({ email: testEmail, @@ -62,6 +65,7 @@ describe("LoginApprovalComponent", () => { { provide: KeyService, useValue: mock() }, { provide: DialogRef, useValue: dialogRef }, { provide: ToastService, useValue: toastService }, + { provide: ValidationService, useValue: validationService }, { provide: LoginApprovalComponentServiceAbstraction, useValue: mock(), diff --git a/libs/auth/src/angular/login-approval/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts index 5192334a0ca..3b44f545abb 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -16,6 +16,7 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { AsyncActionsModule, @@ -40,6 +41,8 @@ export interface LoginApprovalDialogParams { imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule], }) export class LoginApprovalComponent implements OnInit, OnDestroy { + loading = true; + notificationId: string; private destroy$ = new Subject(); @@ -62,25 +65,25 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { private dialogRef: DialogRef, private toastService: ToastService, private loginApprovalComponentService: LoginApprovalComponentService, + private validationService: ValidationService, ) { this.notificationId = params.notificationId; } async ngOnDestroy(): Promise { clearInterval(this.interval); - const closedWithButton = await firstValueFrom(this.dialogRef.closed); - if (!closedWithButton) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.retrieveAuthRequestAndRespond(false); - } this.destroy$.next(); this.destroy$.complete(); } async ngOnInit() { if (this.notificationId != null) { - this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + try { + this.authRequestResponse = await this.apiService.getAuthRequest(this.notificationId); + } catch (error) { + this.validationService.showError(error); + } + const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey); this.email = await await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), @@ -96,6 +99,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { }, RequestTimeUpdate); this.loginApprovalComponentService.showLoginRequestedAlertIfWindowNotVisible(this.email); + + this.loading = false; } } @@ -131,6 +136,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { ); this.showResultToast(loginResponse); } + + this.dialogRef.close(approve); } showResultToast(loginResponse: AuthRequestResponse) { diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 5600077c363..a3f5e062e4f 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -107,7 +107,7 @@ export class LoginDecryptionOptionsComponent implements OnInit { private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private validationService: ValidationService, ) { - this.clientType === this.platformUtilsService.getClientType(); + this.clientType = this.platformUtilsService.getClientType(); } async ngOnInit() { diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 40f85e6d75c..f9aaa5d1e05 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -484,6 +484,8 @@ export class LoginComponent implements OnInit, OnDestroy { try { const deviceIdentifier = await this.appIdService.getAppId(); this.isKnownDevice = await this.devicesApiService.getKnownDevice(email, deviceIdentifier); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.isKnownDevice = false; } diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts index fa3ad2ae2b9..6047cc3d27a 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts @@ -28,7 +28,8 @@ import { } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests"; import { LoginEmailService } from "../../../common"; import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts index 0557227938f..b54529f6a2c 100644 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts +++ b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts @@ -15,6 +15,8 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ToastService } from "../../../../components/src/toast"; import { InputPasswordComponent } from "../input-password/input-password.component"; import { PasswordInputResult } from "../input-password/password-input-result"; diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index aad0df4e397..3bcc56ae4cd 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -12,6 +12,7 @@ import { TrustedDeviceUserDecryptionOption, UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, + LoginSuccessHandlerService, } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; @@ -35,7 +36,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { AsyncActionsModule, ButtonModule, @@ -117,7 +117,7 @@ export class SsoComponent implements OnInit { private accountService: AccountService, private toastService: ToastService, private ssoComponentService: SsoComponentService, - private syncService: SyncService, + private loginSuccessHandlerService: LoginSuccessHandlerService, ) { environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { this.redirectUri = env.getWebVaultUrl() + "/sso-connector.html"; @@ -378,8 +378,7 @@ export class SsoComponent implements OnInit { // Everything after the 2FA check is considered a successful login // Just have to figure out where to send the user - - await this.syncService.fullSync(true); + await this.loginSuccessHandlerService.run(authResult.userId); // Save off the OrgSsoIdentifier for use in the TDE flows (or elsewhere) // - TDE login decryption options component diff --git a/libs/auth/src/common/models/domain/user-decryption-options.ts b/libs/auth/src/common/models/domain/user-decryption-options.ts index 95efe2b0077..00b78064d83 100644 --- a/libs/auth/src/common/models/domain/user-decryption-options.ts +++ b/libs/auth/src/common/models/domain/user-decryption-options.ts @@ -4,6 +4,8 @@ import { Jsonify } from "type-fest"; import { KeyConnectorUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/key-connector-user-decryption-option.response"; import { TrustedDeviceUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { IdentityTokenResponse } from "@bitwarden/common/src/auth/models/response/identity-token.response"; /** diff --git a/libs/auth/src/common/services/login-email/login-email.service.ts b/libs/auth/src/common/services/login-email/login-email.service.ts index feb0cba1bdc..aa13afd5004 100644 --- a/libs/auth/src/common/services/login-email/login-email.service.ts +++ b/libs/auth/src/common/services/login-email/login-email.service.ts @@ -6,6 +6,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { GlobalState, KeyDefinition, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 1f98a117c88..57a653b205e 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -35,6 +35,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { MasterKey } from "@bitwarden/common/types/key"; diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts index ffb660e6a7f..7c44a6f1682 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts @@ -8,6 +8,8 @@ import { USER_DECRYPTION_OPTIONS_DISK, UserKeyDefinition, } from "@bitwarden/common/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction"; diff --git a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts index 717e80b110d..24b3adacc21 100644 --- a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts +++ b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts @@ -18,6 +18,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { try { // Attempt to decode from URL-safe Base64 to UTF-8 decodedPayloadJSON = Utils.fromUrlB64ToUtf8(encodedPayload); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (decodingError) { throw new Error("Cannot decode the token"); } @@ -26,6 +28,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { // Attempt to parse the JSON payload const decodedToken = JSON.parse(decodedPayloadJSON); return decodedToken; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (jsonError) { throw new Error("Cannot parse the token's payload into JSON"); } diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json index 6004a56fb55..9be942d38de 100644 --- a/libs/auth/tsconfig.json +++ b/libs/auth/tsconfig.json @@ -1,5 +1,22 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "resolveJsonModule": true, + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/billing/jest.config.js b/libs/billing/jest.config.js index d9bae9633ea..c43606191b9 100644 --- a/libs/billing/jest.config.js +++ b/libs/billing/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/billing/tsconfig.json b/libs/billing/tsconfig.json index 6004a56fb55..bb08eb89d1c 100644 --- a/libs/billing/tsconfig.json +++ b/libs/billing/tsconfig.json @@ -1,5 +1,6 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": {}, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/common/jest.config.js b/libs/common/jest.config.js index d7f78abbf38..7e6c0997b9c 100644 --- a/libs/common/jest.config.js +++ b/libs/common/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../shared/jest.config.ts"); diff --git a/libs/common/spec/matchers/index.ts b/libs/common/spec/matchers/index.ts index 44440be5b54..b2e09cc8e92 100644 --- a/libs/common/spec/matchers/index.ts +++ b/libs/common/spec/matchers/index.ts @@ -1,16 +1,12 @@ -import * as matchers from "jest-extended"; - import { toBeFulfilled, toBeResolved, toBeRejected } from "./promise-fulfilled"; import { toAlmostEqual } from "./to-almost-equal"; +import { toContainPartialObjects } from "./to-contain-partial-objects"; import { toEqualBuffer } from "./to-equal-buffer"; export * from "./to-equal-buffer"; export * from "./to-almost-equal"; export * from "./promise-fulfilled"; -// add all jest-extended matchers -expect.extend(matchers); - export function addCustomMatchers() { expect.extend({ toEqualBuffer: toEqualBuffer, @@ -18,6 +14,7 @@ export function addCustomMatchers() { toBeFulfilled: toBeFulfilled, toBeResolved: toBeResolved, toBeRejected: toBeRejected, + toContainPartialObjects, }); } @@ -59,4 +56,9 @@ export interface CustomMatchers { * @returns CustomMatcherResult indicating whether or not the test passed */ toBeRejected(withinMs?: number): Promise; + /** + * Matches if the received array contains all the expected objects using partial matching (expect.objectContaining). + * @param expected An array of partial objects that should be contained in the received array. + */ + toContainPartialObjects(expected: Array): R; } diff --git a/libs/common/spec/matchers/to-contain-partial-objects.spec.ts b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts new file mode 100644 index 00000000000..ab6f90adf17 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts @@ -0,0 +1,77 @@ +describe("toContainPartialObjects", () => { + describe("matches", () => { + it("if the array only contains the partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + + it("if the array contains the partial objects and other objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + { + id: 3, + name: "baz", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + }); + + describe("doesn't match", () => { + it("if the array does not contain any partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1, name: "Foo" }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + + it("if the array contains some but not all partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 2 }, { id: 3 }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + }); +}); diff --git a/libs/common/spec/matchers/to-contain-partial-objects.ts b/libs/common/spec/matchers/to-contain-partial-objects.ts new file mode 100644 index 00000000000..f072ca6fba6 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.ts @@ -0,0 +1,31 @@ +import { EOL } from "os"; + +import { diff } from "jest-diff"; + +export const toContainPartialObjects: jest.CustomMatcher = function ( + received: Array, + expected: Array, +) { + const matched = this.equals( + received, + expect.arrayContaining(expected.map((e) => expect.objectContaining(e))), + ); + + if (matched) { + return { + message: () => + "Expected the received array NOT to include partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: true, + }; + } + + return { + message: () => + "Expected the received array to contain partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: false, + }; +}; diff --git a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts index 5ecfca5ab84..5a393ed1996 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction.ts @@ -1,7 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - +import { ListResponse } from "../../../models/response/list.response"; import { OrganizationDomainRequest } from "../../services/organization-domain/requests/organization-domain.request"; import { OrganizationDomainSsoDetailsResponse } from "./responses/organization-domain-sso-details.response"; diff --git a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts index c4817306a63..066d2d381e7 100644 --- a/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts +++ b/libs/common/src/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../../models/response/base.response"; export class VerifiedOrganizationDomainSsoDetailsResponse extends BaseResponse { organizationName: string; diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 0c45c919e95..000d1655416 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -1,7 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; - import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; import { OrganizationSsoRequest } from "../../../auth/models/request/organization-sso.request"; import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; @@ -13,6 +11,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; diff --git a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts index 7497a77e6f2..1052fe504d4 100644 --- a/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts +++ b/libs/common/src/admin-console/services/organization-domain/org-domain-api.service.spec.ts @@ -1,14 +1,13 @@ import { mock } from "jest-mock-extended"; import { lastValueFrom } from "rxjs"; -import { VerifiedOrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; - import { ApiService } from "../../../abstractions/api.service"; +import { ListResponse } from "../../../models/response/list.response"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { OrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { OrganizationDomainResponse } from "../../abstractions/organization-domain/responses/organization-domain.response"; +import { VerifiedOrganizationDomainSsoDetailsResponse } from "../../abstractions/organization-domain/responses/verified-organization-domain-sso-details.response"; import { OrgDomainApiService } from "./org-domain-api.service"; import { OrgDomainService } from "./org-domain.service"; diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index b3fd11982b8..598bb2a29db 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -1,6 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; import { ApiService } from "../../../abstractions/api.service"; import { OrganizationApiKeyRequest } from "../../../admin-console/models/request/organization-api-key.request"; @@ -14,6 +13,7 @@ import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; +import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; diff --git a/libs/common/src/admin-console/services/organization/vnext-organization.state.ts b/libs/common/src/admin-console/services/organization/vnext-organization.state.ts index 48e09d6d076..fea0423f389 100644 --- a/libs/common/src/admin-console/services/organization/vnext-organization.state.ts +++ b/libs/common/src/admin-console/services/organization/vnext-organization.state.ts @@ -1,7 +1,6 @@ import { Jsonify } from "type-fest"; -import { ORGANIZATIONS_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; - +import { ORGANIZATIONS_DISK, UserKeyDefinition } from "../../../platform/state"; import { OrganizationData } from "../../models/data/organization.data"; /** diff --git a/libs/common/src/auth/abstractions/devices/responses/device.response.ts b/libs/common/src/auth/abstractions/devices/responses/device.response.ts index 707616744ad..84a2fb03c28 100644 --- a/libs/common/src/auth/abstractions/devices/responses/device.response.ts +++ b/libs/common/src/auth/abstractions/devices/responses/device.response.ts @@ -1,6 +1,11 @@ import { DeviceType } from "../../../../enums"; import { BaseResponse } from "../../../../models/response/base.response"; +export interface DevicePendingAuthRequest { + id: string; + creationDate: string; +} + export class DeviceResponse extends BaseResponse { id: string; userId: string; @@ -10,7 +15,7 @@ export class DeviceResponse extends BaseResponse { creationDate: string; revisionDate: string; isTrusted: boolean; - devicePendingAuthRequest: { id: string; creationDate: string } | null; + devicePendingAuthRequest: DevicePendingAuthRequest | null; constructor(response: any) { super(response); diff --git a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts index 791c4688078..84103fe5d29 100644 --- a/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts +++ b/libs/common/src/auth/models/request/webauthn-rotate-credential.request.ts @@ -1,8 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet } from "../../../../../auth/src/common/models"; +import { EncString } from "../../../platform/models/domain/enc-string"; export class WebauthnRotateCredentialRequest { id: string; diff --git a/libs/common/src/auth/services/anonymous-hub.service.ts b/libs/common/src/auth/services/anonymous-hub.service.ts index a268c8a2712..3900dd53ee0 100644 --- a/libs/common/src/auth/services/anonymous-hub.service.ts +++ b/libs/common/src/auth/services/anonymous-hub.service.ts @@ -9,6 +9,8 @@ import { import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack"; import { firstValueFrom } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "../../../../auth/src/common/abstractions"; import { NotificationType } from "../../enums"; import { diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts index 5663384714d..4b7b8a2b262 100644 --- a/libs/common/src/auth/services/auth.service.spec.ts +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index bab83fc55db..d855a1be34f 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,6 +11,8 @@ import { switchMap, } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { StateService } from "../../platform/abstractions/state.service"; diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index 409150552ad..a94c8b6e422 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -4,6 +4,8 @@ import { firstValueFrom, map, Observable } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { AppIdService } from "../../platform/abstractions/app-id.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; @@ -335,6 +337,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); return new SymmetricCryptoKey(userKey) as UserKey; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // If either decryption effort fails, we want to remove the device key this.logService.error("Failed to decrypt using device key. Removing device key."); diff --git a/libs/common/src/auth/services/device-trust.service.spec.ts b/libs/common/src/auth/services/device-trust.service.spec.ts index 66a91a693e5..943653e3129 100644 --- a/libs/common/src/auth/services/device-trust.service.spec.ts +++ b/libs/common/src/auth/services/device-trust.service.spec.ts @@ -3,7 +3,11 @@ import { BehaviorSubject, of } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptions } from "../../../../auth/src/common/models/domain/user-decryption-options"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { FakeActiveUserState } from "../../../spec/fake-state"; diff --git a/libs/common/src/auth/services/devices/devices.service.implementation.ts b/libs/common/src/auth/services/devices/devices.service.implementation.ts index cd6f1148dd8..cdaa7a9fc4e 100644 --- a/libs/common/src/auth/services/devices/devices.service.implementation.ts +++ b/libs/common/src/auth/services/devices/devices.service.implementation.ts @@ -1,8 +1,7 @@ import { Observable, defer, map } from "rxjs"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; - import { ListResponse } from "../../../models/response/list.response"; +import { AppIdService } from "../../../platform/abstractions/app-id.service"; import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction"; import { DeviceResponse } from "../../abstractions/devices/responses/device.response"; import { DeviceView } from "../../abstractions/devices/views/device.view"; diff --git a/libs/common/src/auth/services/key-connector.service.spec.ts b/libs/common/src/auth/services/key-connector.service.spec.ts index b1bf87693c1..660f1124f4a 100644 --- a/libs/common/src/auth/services/key-connector.service.spec.ts +++ b/libs/common/src/auth/services/key-connector.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/auth/services/master-password/master-password.service.ts b/libs/common/src/auth/services/master-password/master-password.service.ts index ea6e1045c10..14e7522a836 100644 --- a/libs/common/src/auth/services/master-password/master-password.service.ts +++ b/libs/common/src/auth/services/master-password/master-password.service.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; - import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; +import { LogService } from "../../../platform/abstractions/log.service"; import { StateService } from "../../../platform/abstractions/state.service"; import { EncryptionType } from "../../../platform/enums"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; @@ -180,10 +179,18 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr let decUserKey: Uint8Array; if (userKey.encryptionType === EncryptionType.AesCbc256_B64) { - decUserKey = await this.encryptService.decryptToBytes(userKey, masterKey); + decUserKey = await this.encryptService.decryptToBytes( + userKey, + masterKey, + "Content: User Key; Encrypting Key: Master Key", + ); } else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { const newKey = await this.keyGenerationService.stretchKey(masterKey); - decUserKey = await this.encryptService.decryptToBytes(userKey, newKey); + decUserKey = await this.encryptService.decryptToBytes( + userKey, + newKey, + "Content: User Key; Encrypting Key: Stretched Master Key", + ); } else { throw new Error("Unsupported encryption type."); } diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index ed622b21c86..ddd24ae7907 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -2,13 +2,15 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { UserId } from "../../../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; +import { UserId } from "../../types/guid"; import { Account, AccountInfo, AccountService } from "../abstractions/account.service"; import { PasswordResetEnrollmentServiceImplementation } from "./password-reset-enrollment.service.implementation"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index c5451ce30c6..22d5384e6ac 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -6,10 +6,12 @@ import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { Utils } from "../../platform/misc/utils"; import { UserKey } from "../../types/key"; diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index 081dafb1706..102e4bac8da 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -14,6 +14,8 @@ import { KeyService, } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KdfConfigService } from "../../../../../key-management/src/abstractions/kdf-config.service"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; import { VaultTimeoutSettingsService } from "../../../abstractions/vault-timeout/vault-timeout-settings.service"; diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 2935c1958a4..4735da32b6b 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -10,6 +10,8 @@ import { KeyService, } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin.service.abstraction"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { HashPurpose } from "../../../platform/enums"; @@ -161,6 +163,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti const request = new VerifyOTPRequest(verification.secret); try { await this.userVerificationApiService.postAccountVerifyOTP(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidVerificationCode")); } @@ -219,6 +223,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti request.masterPasswordHash = serverKeyHash; try { policyOptions = await this.userVerificationApiService.postAccountVerifyPassword(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidMasterPassword")); } diff --git a/libs/common/src/autofill/services/domain-settings.service.spec.ts b/libs/common/src/autofill/services/domain-settings.service.spec.ts index a25653f168c..36f7d0eacec 100644 --- a/libs/common/src/autofill/services/domain-settings.service.spec.ts +++ b/libs/common/src/autofill/services/domain-settings.service.spec.ts @@ -1,9 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; @@ -29,7 +28,7 @@ describe("DefaultDomainSettingsService", () => { jest.spyOn(domainSettingsService, "getUrlEquivalentDomains"); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); - domainSettingsService.blockedInteractionsUris$ = of(null); + domainSettingsService.blockedInteractionsUris$ = of({}); }); describe("getUrlEquivalentDomains", () => { diff --git a/libs/common/src/autofill/services/domain-settings.service.ts b/libs/common/src/autofill/services/domain-settings.service.ts index aeb3af69dae..b2833b9ee25 100644 --- a/libs/common/src/autofill/services/domain-settings.service.ts +++ b/libs/common/src/autofill/services/domain-settings.service.ts @@ -2,8 +2,7 @@ // @ts-strict-ignore import { map, Observable, switchMap, of } from "rxjs"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; - +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { NeverDomains, EquivalentDomains, @@ -36,7 +35,7 @@ const BLOCKED_INTERACTIONS_URIS = new KeyDefinition( DOMAIN_SETTINGS_DISK, "blockedInteractionsUris", { - deserializer: (value: NeverDomains) => value ?? null, + deserializer: (value: NeverDomains) => value ?? {}, }, ); @@ -131,7 +130,7 @@ export class DefaultDomainSettingsService implements DomainSettingsService { switchMap((featureIsEnabled) => featureIsEnabled ? this.blockedInteractionsUrisState.state$ : of({} as NeverDomains), ), - map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : null)), + map((disabledUris) => (Object.keys(disabledUris).length ? disabledUris : {})), ); this.equivalentDomainsState = this.stateProvider.getActive(EQUIVALENT_DOMAINS); diff --git a/libs/common/src/autofill/utils.spec.ts b/libs/common/src/autofill/utils.spec.ts index 4dd36ba7d89..554dc973b48 100644 --- a/libs/common/src/autofill/utils.spec.ts +++ b/libs/common/src/autofill/utils.spec.ts @@ -1,9 +1,6 @@ -import { - normalizeExpiryYearFormat, - isCardExpired, - parseYearMonthExpiry, -} from "@bitwarden/common/autofill/utils"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { CardView } from "../vault/models/view/card.view"; + +import { normalizeExpiryYearFormat, isCardExpired, parseYearMonthExpiry } from "./utils"; function getExpiryYearValueFormats(currentCentury: string) { return [ diff --git a/libs/common/src/autofill/utils.ts b/libs/common/src/autofill/utils.ts index 6bee5e1a198..a77ea8a715d 100644 --- a/libs/common/src/autofill/utils.ts +++ b/libs/common/src/autofill/utils.ts @@ -1,11 +1,12 @@ +import { CardView } from "../vault/models/view/card.view"; + import { DelimiterPatternExpression, ExpiryFullYearPattern, ExpiryFullYearPatternExpression, IrrelevantExpiryCharactersPatternExpression, MonthPatternExpression, -} from "@bitwarden/common/autofill/constants"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +} from "./constants"; type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; diff --git a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts index 06400081e11..e0e8b7377c5 100644 --- a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts @@ -3,7 +3,7 @@ import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class AccountBillingApiServiceAbstraction { getBillingInvoices: (status?: string, startAfter?: string) => Promise; diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index 4b08b52a136..928f65a3636 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -1,20 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export abstract class BillingApiServiceAbstraction { diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index 7c4e0a39f8f..1e68488ac98 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -1,11 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response"; -import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; import { InitiationPath } from "../../models/request/reference-event.request"; import { PaymentMethodType, PlanType } from "../enums"; +import { BillingSourceResponse } from "../models/response/billing.response"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; export type OrganizationInformation = { name: string; diff --git a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts index 6a72724a5ec..2ed25491049 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts @@ -3,7 +3,7 @@ import { BillingInvoiceResponse, BillingTransactionResponse, -} from "@bitwarden/common/billing/models/response/billing.response"; +} from "../../models/response/billing.response"; export class OrganizationBillingApiServiceAbstraction { getBillingInvoices: ( diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts index 438d3f394e0..7a744dae856 100644 --- a/libs/common/src/billing/abstractions/tax.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/tax.service.abstraction.ts @@ -1,7 +1,7 @@ -import { CountryListItem } from "@bitwarden/common/billing/models/domain"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; export abstract class TaxServiceAbstraction { abstract getCountries(): CountryListItem[]; diff --git a/libs/common/src/billing/models/domain/tax-information.ts b/libs/common/src/billing/models/domain/tax-information.ts index 78e1bcc42b5..794cdef3ed4 100644 --- a/libs/common/src/billing/models/domain/tax-information.ts +++ b/libs/common/src/billing/models/domain/tax-information.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; +import { TaxInfoResponse } from "../response/tax-info.response"; export class TaxInformation { country: string; diff --git a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts index 784d2691629..83b254ac512 100644 --- a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts +++ b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TaxInformation } from "@bitwarden/common/billing/models/domain/tax-information"; +import { TaxInformation } from "../domain/tax-information"; import { TaxInfoUpdateRequest } from "./tax-info-update.request"; diff --git a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts index 365dff5c110..40d8db03d3b 100644 --- a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts +++ b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts @@ -1,4 +1,4 @@ -import { PlanType } from "@bitwarden/common/billing/enums"; +import { PlanType } from "../../enums"; export class PreviewOrganizationInvoiceRequest { organizationId?: string; diff --git a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts index c740e4157ed..e4bf575cc6a 100644 --- a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts +++ b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { PaymentMethodType } from "../../enums"; export class TokenizedPaymentSourceRequest { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/request/update-payment-method.request.ts b/libs/common/src/billing/models/request/update-payment-method.request.ts index 9ef91ae579b..10b03103716 100644 --- a/libs/common/src/billing/models/request/update-payment-method.request.ts +++ b/libs/common/src/billing/models/request/update-payment-method.request.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; +import { ExpandedTaxInfoUpdateRequest } from "./expanded-tax-info-update.request"; +import { TokenizedPaymentSourceRequest } from "./tokenized-payment-source.request"; export class UpdatePaymentMethodRequest { paymentSource: TokenizedPaymentSourceRequest; diff --git a/libs/common/src/billing/models/response/invoices.response.ts b/libs/common/src/billing/models/response/invoices.response.ts index bf797ba42d6..05c95b83c6f 100644 --- a/libs/common/src/billing/models/response/invoices.response.ts +++ b/libs/common/src/billing/models/response/invoices.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class InvoicesResponse extends BaseResponse { invoices: InvoiceResponse[] = []; diff --git a/libs/common/src/billing/models/response/payment-source.response.ts b/libs/common/src/billing/models/response/payment-source.response.ts index 1aeeb450b11..93418fc2f55 100644 --- a/libs/common/src/billing/models/response/payment-source.response.ts +++ b/libs/common/src/billing/models/response/payment-source.response.ts @@ -1,5 +1,5 @@ -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; +import { PaymentMethodType } from "../../enums"; export class PaymentSourceResponse extends BaseResponse { type: PaymentMethodType; diff --git a/libs/common/src/billing/models/response/preview-invoice.response.ts b/libs/common/src/billing/models/response/preview-invoice.response.ts index c822a569bb3..efd3da3e9f1 100644 --- a/libs/common/src/billing/models/response/preview-invoice.response.ts +++ b/libs/common/src/billing/models/response/preview-invoice.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class PreviewInvoiceResponse extends BaseResponse { effectiveTaxRate: number; diff --git a/libs/common/src/billing/models/response/provider-subscription-response.ts b/libs/common/src/billing/models/response/provider-subscription-response.ts index 2ecf988addd..4481f7588ff 100644 --- a/libs/common/src/billing/models/response/provider-subscription-response.ts +++ b/libs/common/src/billing/models/response/provider-subscription-response.ts @@ -1,9 +1,9 @@ -import { ProviderType } from "@bitwarden/common/admin-console/enums"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { SubscriptionSuspensionResponse } from "@bitwarden/common/billing/models/response/subscription-suspension.response"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; - +import { ProviderType } from "../../../admin-console/enums"; import { BaseResponse } from "../../../models/response/base.response"; +import { PlanType, ProductTierType } from "../../enums"; + +import { SubscriptionSuspensionResponse } from "./subscription-suspension.response"; +import { TaxInfoResponse } from "./tax-info.response"; export class ProviderSubscriptionResponse extends BaseResponse { status: string; diff --git a/libs/common/src/billing/models/response/subscription-suspension.response.ts b/libs/common/src/billing/models/response/subscription-suspension.response.ts index 418e1c443c8..3d714a05dba 100644 --- a/libs/common/src/billing/models/response/subscription-suspension.response.ts +++ b/libs/common/src/billing/models/response/subscription-suspension.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class SubscriptionSuspensionResponse extends BaseResponse { suspensionDate: string; diff --git a/libs/common/src/billing/models/response/tax-id-types.response.ts b/libs/common/src/billing/models/response/tax-id-types.response.ts index 0d5cce46c8c..f31f2133b34 100644 --- a/libs/common/src/billing/models/response/tax-id-types.response.ts +++ b/libs/common/src/billing/models/response/tax-id-types.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BaseResponse } from "../../../models/response/base.response"; export class TaxIdTypesResponse extends BaseResponse { taxIdTypes: TaxIdTypeResponse[] = []; diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts index 372d8099865..02dbef469d6 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.spec.ts @@ -1,17 +1,16 @@ import { firstValueFrom } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - import { FakeAccountService, mockAccountServiceWith, FakeStateProvider, FakeSingleUserState, } from "../../../../spec"; +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { UserId } from "../../../types/guid"; import { BillingAccountProfile } from "../../abstractions/account/billing-account-profile-state.service"; +import { BillingHistoryResponse } from "../../models/response/billing-history.response"; import { BILLING_ACCOUNT_PROFILE_KEY_DEFINITION, diff --git a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts index 579a81eeb5c..155ce1493b4 100644 --- a/libs/common/src/billing/services/account/billing-account-profile-state.service.ts +++ b/libs/common/src/billing/services/account/billing-account-profile-state.service.ts @@ -1,8 +1,7 @@ import { map, Observable, combineLatest, concatMap } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - +import { ApiService } from "../../../abstractions/api.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { BILLING_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 7ce5602f3cc..4306324395e 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -1,25 +1,25 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; -import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ToastService } from "@bitwarden/components"; import { ApiService } from "../../abstractions/api.service"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; -import { BillingApiServiceAbstraction } from "../../billing/abstractions"; -import { PaymentMethodType } from "../../billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request"; -import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; -import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; -import { PlanResponse } from "../../billing/models/response/plan.response"; +import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; +import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; +import { LogService } from "../../platform/abstractions/log.service"; +import { BillingApiServiceAbstraction } from "../abstractions"; +import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; +import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; +import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; +import { InvoicesResponse } from "../models/response/invoices.response"; +import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response"; +import { PaymentMethodResponse } from "../models/response/payment-method.response"; +import { PlanResponse } from "../models/response/plan.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export class BillingApiService implements BillingApiServiceAbstraction { diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index ca10b368662..da1a1666ff0 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -1,18 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { - BillingApiServiceAbstraction, - OrganizationBillingServiceAbstraction, - OrganizationInformation, - PaymentInformation, - PlanInformation, - SubscriptionInformation, -} from "@bitwarden/common/billing/abstractions"; -import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response"; -import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; @@ -20,12 +7,25 @@ import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../ import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { SyncService } from "../../platform/sync"; import { OrgKey } from "../../types/key"; +import { + BillingApiServiceAbstraction, + OrganizationBillingServiceAbstraction, + OrganizationInformation, + PaymentInformation, + PlanInformation, + SubscriptionInformation, +} from "../abstractions"; import { PlanType } from "../enums"; import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request"; +import { BillingSourceResponse } from "../models/response/billing.response"; +import { PaymentSourceResponse } from "../models/response/payment-source.response"; interface OrganizationKeys { encryptedKey: EncString; diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts index 45e57267ec0..aa27c99adc8 100644 --- a/libs/common/src/billing/services/tax.service.ts +++ b/libs/common/src/billing/services/tax.service.ts @@ -1,9 +1,9 @@ -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { CountryListItem } from "@bitwarden/common/billing/models/domain"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { PreviewInvoiceResponse } from "@bitwarden/common/billing/models/response/preview-invoice.response"; +import { ApiService } from "../../abstractions/api.service"; +import { TaxServiceAbstraction } from "../abstractions/tax.service.abstraction"; +import { CountryListItem } from "../models/domain"; +import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; +import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; export class TaxService implements TaxServiceAbstraction { constructor(private apiService: ApiService) {} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 0ab7d47acfc..dde31acb9e3 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -4,6 +4,18 @@ * Flags MUST be short lived and SHALL be removed once enabled. */ export enum FeatureFlag { + /* Autofill */ + BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", + DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", + EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", + GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", + InlineMenuFieldQualification = "inline-menu-field-qualification", + InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", + InlineMenuTotp = "inline-menu-totp", + NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", + NotificationRefresh = "notification-refresh", + UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", + BrowserFilelessImport = "browser-fileless-import", ItemShare = "item-share", GeneratorToolsModernization = "generator-tools-modernization", @@ -11,23 +23,15 @@ export enum FeatureFlag { ExtensionRefresh = "extension-refresh", PersistPopupView = "persist-popup-view", PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", - UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", EmailVerification = "email-verification", - InlineMenuFieldQualification = "inline-menu-field-qualification", TwoFactorComponentRefactor = "two-factor-component-refactor", - InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", VaultBulkManagementAction = "vault-bulk-management-action", IdpAutoSubmitLogin = "idp-auto-submit-login", UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh", - GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", - EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", - DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", AccountDeprovisioning = "pm-10308-account-deprovisioning", SSHKeyVaultItem = "ssh-key-vault-item", SSHAgent = "ssh-agent", - NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", - BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api", CipherKeyEncryption = "cipher-key-encryption", VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", @@ -39,7 +43,6 @@ export enum FeatureFlag { NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", - InlineMenuTotp = "inline-menu-totp", MacOsNativeCredentialSync = "macos-native-credential-sync", PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", @@ -59,6 +62,18 @@ const FALSE = false as boolean; * We support true as a value as we prefer flags to "enable" not "disable". */ export const DefaultFeatureFlagValue = { + /* Autofill */ + [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, + [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, + [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, + [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, + [FeatureFlag.InlineMenuFieldQualification]: FALSE, + [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, + [FeatureFlag.InlineMenuTotp]: FALSE, + [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, + [FeatureFlag.NotificationRefresh]: FALSE, + [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, + [FeatureFlag.BrowserFilelessImport]: FALSE, [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.GeneratorToolsModernization]: FALSE, @@ -66,23 +81,15 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.ExtensionRefresh]: FALSE, [FeatureFlag.PersistPopupView]: FALSE, [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, - [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.EmailVerification]: FALSE, - [FeatureFlag.InlineMenuFieldQualification]: FALSE, [FeatureFlag.TwoFactorComponentRefactor]: FALSE, - [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE, - [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, - [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, - [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, [FeatureFlag.AccountDeprovisioning]: FALSE, [FeatureFlag.SSHKeyVaultItem]: FALSE, [FeatureFlag.SSHAgent]: FALSE, - [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, - [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, [FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, @@ -94,7 +101,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, - [FeatureFlag.InlineMenuTotp]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, [FeatureFlag.PM12443RemovePagingLogic]: FALSE, diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts index 69cbdff9dd2..db59fcafa69 100644 --- a/libs/common/src/enums/notification-type.enum.ts +++ b/libs/common/src/enums/notification-type.enum.ts @@ -23,4 +23,5 @@ export enum NotificationType { SyncOrganizations = 17, SyncOrganizationStatusChanged = 18, + SyncOrganizationCollectionSettingChanged = 19, } diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 8c1d1117c89..9f97e0a94c1 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -2,16 +2,18 @@ // @ts-strict-ignore import { firstValueFrom, map, timeout } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../../../auth/src/common/abstractions"; import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { AccountService } from "../../auth/abstractions/account.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; +import { MessagingService } from "../../platform/abstractions/messaging.service"; import { UserId } from "../../types/guid"; import { ProcessReloadServiceAbstraction } from "../abstractions/process-reload.service"; @@ -37,6 +39,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract let status = await firstValueFrom(authService.authStatusFor$(userId as UserId)); status = await authService.getAuthStatus(userId); if (status === AuthenticationStatus.Unlocked) { + this.logService.info( + "[Process Reload Service] User unlocked, preventing process reload", + ); return; } } @@ -53,6 +58,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract if (userId != null) { const ephemeralPin = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId); if (ephemeralPin != null) { + this.logService.info( + "[Process Reload Service] Ephemeral pin active, preventing process reload", + ); return; } } @@ -95,7 +103,12 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract await this.reloadCallback(); } return; + } else { + this.logService.info( + "[Process Reload Service] Desktop ipc fingerprint validated, preventing process reload", + ); } + if (this.reloadInterval == null) { this.reloadInterval = setInterval(async () => await this.executeProcessReload(), 1000); } diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts index c502ddafd47..a99ebac34b3 100644 --- a/libs/common/src/models/export/ssh-key.export.ts +++ b/libs/common/src/models/export/ssh-key.export.ts @@ -1,9 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { SshKeyView as SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { EncString } from "../../platform/models/domain/enc-string"; import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key"; +import { SshKeyView as SshKeyView } from "../../vault/models/view/ssh-key.view"; import { safeGetString } from "./utils"; diff --git a/libs/common/src/models/export/utils.ts b/libs/common/src/models/export/utils.ts index 630b4898503..b7e0b74b611 100644 --- a/libs/common/src/models/export/utils.ts +++ b/libs/common/src/models/export/utils.ts @@ -1,4 +1,4 @@ -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncString } from "../../platform/models/domain/enc-string"; export function safeGetString(value: string | EncString) { if (value == null) { diff --git a/libs/common/src/models/response/notification.response.ts b/libs/common/src/models/response/notification.response.ts index 473e6fc1d10..894a00ee885 100644 --- a/libs/common/src/models/response/notification.response.ts +++ b/libs/common/src/models/response/notification.response.ts @@ -45,6 +45,9 @@ export class NotificationResponse extends BaseResponse { case NotificationType.SyncOrganizationStatusChanged: this.payload = new OrganizationStatusPushNotification(payload); break; + case NotificationType.SyncOrganizationCollectionSettingChanged: + this.payload = new OrganizationCollectionSettingChangedPushNotification(payload); + break; default: break; } @@ -126,3 +129,17 @@ export class OrganizationStatusPushNotification extends BaseResponse { this.enabled = this.getResponseProperty("Enabled"); } } + +export class OrganizationCollectionSettingChangedPushNotification extends BaseResponse { + organizationId: string; + limitCollectionCreation: boolean; + limitCollectionDeletion: boolean; + + constructor(response: any) { + super(response); + + this.organizationId = this.getResponseProperty("OrganizationId"); + this.limitCollectionCreation = this.getResponseProperty("LimitCollectionCreation"); + this.limitCollectionDeletion = this.getResponseProperty("LimitCollectionDeletion"); + } +} diff --git a/libs/common/src/platform/abstractions/crypto-function.service.ts b/libs/common/src/platform/abstractions/crypto-function.service.ts index 18c14677dd0..56b0ee55afe 100644 --- a/libs/common/src/platform/abstractions/crypto-function.service.ts +++ b/libs/common/src/platform/abstractions/crypto-function.service.ts @@ -1,5 +1,5 @@ import { CsprngArray } from "../../types/csprng"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { CbcDecryptParameters, EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export abstract class CryptoFunctionService { @@ -51,11 +51,13 @@ export abstract class CryptoFunctionService { iv: string, mac: string, key: SymmetricCryptoKey, - ): DecryptParameters; - abstract aesDecryptFast( - parameters: DecryptParameters, - mode: "cbc" | "ecb", - ): Promise; + ): CbcDecryptParameters; + abstract aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise; abstract aesDecrypt( data: Uint8Array, iv: Uint8Array, diff --git a/libs/common/src/platform/abstractions/encrypt.service.ts b/libs/common/src/platform/abstractions/encrypt.service.ts index 5b28b98803b..a660524699d 100644 --- a/libs/common/src/platform/abstractions/encrypt.service.ts +++ b/libs/common/src/platform/abstractions/encrypt.service.ts @@ -8,12 +8,32 @@ import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export abstract class EncryptService { abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise; abstract encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an EncString to a string + * @param encString - The EncString to decrypt + * @param key - The key to decrypt the EncString with + * @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include + * sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt + * @returns The decrypted string + */ abstract decryptToUtf8( encString: EncString, key: SymmetricCryptoKey, - decryptContext?: string, + decryptTrace?: string, ): Promise; - abstract decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an Encrypted object to a Uint8Array + * @param encThing - The Encrypted object to decrypt + * @param key - The key to decrypt the Encrypted object with + * @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include + * sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt + * @returns The decrypted Uint8Array + */ + abstract decryptToBytes( + encThing: Encrypted, + key: SymmetricCryptoKey, + decryptTrace?: string, + ): Promise; abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise; abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise; abstract resolveLegacyKey(key: SymmetricCryptoKey, encThing: Encrypted): SymmetricCryptoKey; diff --git a/libs/common/src/platform/misc/flags.ts b/libs/common/src/platform/misc/flags.ts index 8ed19ce57fc..30531b6799e 100644 --- a/libs/common/src/platform/misc/flags.ts +++ b/libs/common/src/platform/misc/flags.ts @@ -1,14 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedFlags = { sdk?: boolean; prereleaseBuild?: boolean; }; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedDevFlags = { noopNotifications: boolean; skipWelcomeOnInstall: boolean; diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index d2bf11a20ce..f654897e9e2 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -8,10 +8,14 @@ import { Observable, of, switchMap } from "rxjs"; import { getHostname, parse } from "tldts"; import { Merge } from "type-fest"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports const nodeURL = typeof self === "undefined" ? require("url") : null; declare global { @@ -608,6 +612,8 @@ export class Utils { } return new URL(uriString); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error } diff --git a/libs/common/src/platform/models/domain/decrypt-parameters.ts b/libs/common/src/platform/models/domain/decrypt-parameters.ts index 784826d3bd2..d3b4bf60d42 100644 --- a/libs/common/src/platform/models/domain/decrypt-parameters.ts +++ b/libs/common/src/platform/models/domain/decrypt-parameters.ts @@ -1,10 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -export class DecryptParameters { +export type CbcDecryptParameters = { encKey: T; data: T; iv: T; - macKey: T; - mac: T; + macKey?: T; + mac?: T; macData: T; -} +}; + +export type EcbDecryptParameters = { + encKey: T; + data: T; +}; diff --git a/libs/common/src/platform/models/domain/domain-base.spec.ts b/libs/common/src/platform/models/domain/domain-base.spec.ts index 0bdee21e3c1..80a4e5e8606 100644 --- a/libs/common/src/platform/models/domain/domain-base.spec.ts +++ b/libs/common/src/platform/models/domain/domain-base.spec.ts @@ -67,9 +67,13 @@ describe("DomainBase", () => { ); // @ts-expect-error -- encString2 was not decrypted + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: string; plainText: string }; // encString2 was not decrypted, so it's still an EncString + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: EncString; plainText: string }; }); diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index bd9139999b7..110a1dc7208 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -8,7 +8,7 @@ import { EncryptService } from "../../abstractions/encrypt.service"; import { EncString } from "./enc-string"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type type EncStringKeys = ConditionalKeys, EncString>; export type DecryptedObject< TEncryptedObject, @@ -63,6 +63,7 @@ export default class Domain { map: any, orgId: string, key: SymmetricCryptoKey = null, + objectContext: string = "No Domain Context", ): Promise { const promises = []; const self: any = this; @@ -78,7 +79,11 @@ export default class Domain { .then(() => { const mapProp = map[theProp] || theProp; if (self[mapProp]) { - return self[mapProp].decrypt(orgId, key); + return self[mapProp].decrypt( + orgId, + key, + `Property: ${prop}; ObjectContext: ${objectContext}`, + ); } return null; }) @@ -114,12 +119,21 @@ export default class Domain { key: SymmetricCryptoKey, encryptService: EncryptService, _: Constructor = this.constructor as Constructor, + objectContext: string = "No Domain Context", ): Promise> { const promises = []; for (const prop of encryptedProperties) { const value = (this as any)[prop] as EncString; - promises.push(this.decryptProperty(prop, value, key, encryptService)); + promises.push( + this.decryptProperty( + prop, + value, + key, + encryptService, + `Property: ${prop.toString()}; ObjectContext: ${objectContext}`, + ), + ); } const decryptedObjects = await Promise.all(promises); @@ -137,10 +151,11 @@ export default class Domain { value: EncString, key: SymmetricCryptoKey, encryptService: EncryptService, + decryptTrace: string, ) { let decrypted: string = null; if (value) { - decrypted = await value.decryptWithKey(key, encryptService); + decrypted = await value.decryptWithKey(key, encryptService, decryptTrace); } else { decrypted = null; } diff --git a/libs/common/src/platform/models/domain/enc-string.spec.ts b/libs/common/src/platform/models/domain/enc-string.spec.ts index 85108a9609b..b4916b9f70a 100644 --- a/libs/common/src/platform/models/domain/enc-string.spec.ts +++ b/libs/common/src/platform/models/domain/enc-string.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeEncString, makeStaticByteArray } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; diff --git a/libs/common/src/platform/models/domain/enc-string.ts b/libs/common/src/platform/models/domain/enc-string.ts index c484c80ee5b..f148664a4f9 100644 --- a/libs/common/src/platform/models/domain/enc-string.ts +++ b/libs/common/src/platform/models/domain/enc-string.ts @@ -125,6 +125,8 @@ export class EncString implements Encrypted { try { encType = parseInt(headerPieces[0], null); encPieces = headerPieces[1].split("|"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return { encType: NaN, encPieces: [] }; } @@ -156,21 +158,21 @@ export class EncString implements Encrypted { return EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE[encType] === encPieces.length; } - async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise { + async decrypt(orgId: string, key: SymmetricCryptoKey = null, context?: string): Promise { if (this.decryptedValue != null) { return this.decryptedValue; } - let keyContext = "provided-key"; + let decryptTrace = "provided-key"; try { if (key == null) { key = await this.getKeyForDecryption(orgId); - keyContext = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey"; + decryptTrace = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey"; if (orgId != null) { - keyContext = `domain-orgkey-${orgId}`; + decryptTrace = `domain-orgkey-${orgId}`; } else { const cryptoService = Utils.getContainerService().getKeyService(); - keyContext = + decryptTrace = (await cryptoService.getUserKey()) == null ? "domain-withlegacysupport-masterkey" : "domain-withlegacysupport-userkey"; @@ -181,20 +183,32 @@ export class EncString implements Encrypted { } const encryptService = Utils.getContainerService().getEncryptService(); - this.decryptedValue = await encryptService.decryptToUtf8(this, key, keyContext); + this.decryptedValue = await encryptService.decryptToUtf8( + this, + key, + decryptTrace == null ? context : `${decryptTrace}${context || ""}`, + ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } return this.decryptedValue; } - async decryptWithKey(key: SymmetricCryptoKey, encryptService: EncryptService) { + async decryptWithKey( + key: SymmetricCryptoKey, + encryptService: EncryptService, + decryptTrace: string = "domain-withkey", + ): Promise { try { if (key == null) { throw new Error("No key to decrypt EncString"); } - this.decryptedValue = await encryptService.decryptToUtf8(this, key, "domain-withkey"); + this.decryptedValue = await encryptService.decryptToUtf8(this, key, decryptTrace); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index f467cb8d6e4..eab4c7b2114 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -7,7 +7,7 @@ import { EncryptionType } from "../../enums"; export class SymmetricCryptoKey { key: Uint8Array; - encKey?: Uint8Array; + encKey: Uint8Array; macKey?: Uint8Array; encType: EncryptionType; @@ -48,12 +48,8 @@ export class SymmetricCryptoKey { throw new Error("Unsupported encType/key length."); } - if (this.key != null) { - this.keyB64 = Utils.fromBufferToB64(this.key); - } - if (this.encKey != null) { - this.encKeyB64 = Utils.fromBufferToB64(this.encKey); - } + this.keyB64 = Utils.fromBufferToB64(this.key); + this.encKeyB64 = Utils.fromBufferToB64(this.encKey); if (this.macKey != null) { this.macKeyB64 = Utils.fromBufferToB64(this.macKey); } diff --git a/libs/common/src/platform/services/container.service.ts b/libs/common/src/platform/services/container.service.ts index 6022e097ab0..c3e727a2e1e 100644 --- a/libs/common/src/platform/services/container.service.ts +++ b/libs/common/src/platform/services/container.service.ts @@ -1,3 +1,5 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { EncryptService } from "../abstractions/encrypt.service"; diff --git a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts index 0a85b34eba8..68263cadf27 100644 --- a/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts +++ b/libs/common/src/platform/services/cryptography/encrypt.service.implementation.ts @@ -114,7 +114,7 @@ export class EncryptServiceImplementation implements EncryptService { const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); if (!macsEqual) { this.logMacFailed( - "[Encrypt service] MAC comparison failed. Key or payload has changed. Key type " + + "[Encrypt service] decryptToUtf8 MAC comparison failed. Key or payload has changed. Key type " + encryptionTypeName(key.encType) + "Payload type " + encryptionTypeName(encString.encryptionType) + @@ -125,10 +125,14 @@ export class EncryptServiceImplementation implements EncryptService { } } - return await this.cryptoFunctionService.aesDecryptFast(fastParams, "cbc"); + return await this.cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters: fastParams }); } - async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise { + async decryptToBytes( + encThing: Encrypted, + key: SymmetricCryptoKey, + decryptContext: string = "no context", + ): Promise { if (key == null) { throw new Error("No encryption key provided."); } @@ -145,7 +149,9 @@ export class EncryptServiceImplementation implements EncryptService { "[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -155,7 +161,9 @@ export class EncryptServiceImplementation implements EncryptService { "[Encrypt service] Key encryption type does not match payload encryption type. Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -167,11 +175,13 @@ export class EncryptServiceImplementation implements EncryptService { const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256"); if (computedMac === null) { this.logMacFailed( - "[Encrypt service] Failed to compute MAC." + + "[Encrypt service#decryptToBytes] Failed to compute MAC." + " Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } @@ -179,11 +189,13 @@ export class EncryptServiceImplementation implements EncryptService { const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac); if (!macsMatch) { this.logMacFailed( - "[Encrypt service] MAC comparison failed. Key or payload has changed." + + "[Encrypt service#decryptToBytes]: MAC comparison failed. Key or payload has changed." + " Key type " + encryptionTypeName(key.encType) + " Payload type " + - encryptionTypeName(encThing.encryptionType), + encryptionTypeName(encThing.encryptionType) + + " Decrypt context: " + + decryptContext, ); return null; } diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts index 23a8ba3138b..16b3968045a 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { DefaultKeyService } from "../../../../key-management/src/key.service"; import { CsprngArray } from "../../types/csprng"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts index abb8993c39c..a8947a49f45 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -1,3 +1,5 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { UserId } from "../../types/guid"; import { KeySuffixOptions } from "../enums"; diff --git a/libs/common/src/platform/services/web-crypto-function.service.spec.ts b/libs/common/src/platform/services/web-crypto-function.service.spec.ts index 71f2828855f..1929e6454ef 100644 --- a/libs/common/src/platform/services/web-crypto-function.service.spec.ts +++ b/libs/common/src/platform/services/web-crypto-function.service.spec.ts @@ -2,7 +2,7 @@ import { mock } from "jest-mock-extended"; import { Utils } from "../../platform/misc/utils"; import { PlatformUtilsService } from "../abstractions/platform-utils.service"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { WebCryptoFunctionService } from "./web-crypto-function.service"; @@ -253,8 +253,13 @@ describe("WebCrypto Function Service", () => { const encData = Utils.fromBufferToB64(encValue); const b64Iv = Utils.fromBufferToB64(iv); const symKey = new SymmetricCryptoKey(key); - const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = cryptoFunctionService.aesDecryptFastParameters( + encData, + b64Iv, + null, + symKey, + ); + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe(value); }); @@ -276,8 +281,8 @@ describe("WebCrypto Function Service", () => { const iv = Utils.fromBufferToB64(makeStaticByteArray(16)); const symKey = new SymmetricCryptoKey(makeStaticByteArray(32)); const data = "ByUF8vhyX4ddU9gcooznwA=="; - const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -287,10 +292,11 @@ describe("WebCrypto Function Service", () => { const cryptoFunctionService = getWebCryptoFunctionService(); const key = makeStaticByteArray(32); const data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw=="); - const params = new DecryptParameters(); - params.encKey = Utils.fromBufferToByteString(key); - params.data = Utils.fromBufferToByteString(data); - const decValue = await cryptoFunctionService.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + encKey: Utils.fromBufferToByteString(key), + data: Utils.fromBufferToByteString(data), + }; + const decValue = await cryptoFunctionService.aesDecryptFast({ mode: "ecb", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -304,6 +310,15 @@ describe("WebCrypto Function Service", () => { const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key, "cbc"); expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); }); + + it("throws if iv is not provided", async () => { + const cryptoFunctionService = getWebCryptoFunctionService(); + const key = makeStaticByteArray(32); + const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA=="); + await expect(() => cryptoFunctionService.aesDecrypt(data, null, key, "cbc")).rejects.toThrow( + "IV is required for CBC mode", + ); + }); }); describe("aesDecrypt ECB mode", () => { diff --git a/libs/common/src/platform/services/web-crypto-function.service.ts b/libs/common/src/platform/services/web-crypto-function.service.ts index c0592654849..61edf7a13b1 100644 --- a/libs/common/src/platform/services/web-crypto-function.service.ts +++ b/libs/common/src/platform/services/web-crypto-function.service.ts @@ -1,12 +1,10 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import * as argon2 from "argon2-browser"; import * as forge from "node-forge"; import { Utils } from "../../platform/misc/utils"; import { CsprngArray } from "../../types/csprng"; import { CryptoFunctionService } from "../abstractions/crypto-function.service"; -import { DecryptParameters } from "../models/domain/decrypt-parameters"; +import { CbcDecryptParameters, EcbDecryptParameters } from "../models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export class WebCryptoFunctionService implements CryptoFunctionService { @@ -14,10 +12,14 @@ export class WebCryptoFunctionService implements CryptoFunctionService { private subtle: SubtleCrypto; private wasmSupported: boolean; - constructor(globalContext: Window | typeof global) { - this.crypto = typeof globalContext.crypto !== "undefined" ? globalContext.crypto : null; - this.subtle = - !!this.crypto && typeof this.crypto.subtle !== "undefined" ? this.crypto.subtle : null; + constructor(globalContext: { crypto: Crypto }) { + if (globalContext?.crypto?.subtle == null) { + throw new Error( + "Could not instantiate WebCryptoFunctionService. Could not locate Subtle crypto.", + ); + } + this.crypto = globalContext.crypto; + this.subtle = this.crypto.subtle; this.wasmSupported = this.checkIfWasmSupported(); } @@ -220,7 +222,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService { hmac.update(a); const mac1 = hmac.digest().getBytes(); - hmac.start(null, null); + hmac.start("sha256", null); hmac.update(b); const mac2 = hmac.digest().getBytes(); @@ -239,10 +241,10 @@ export class WebCryptoFunctionService implements CryptoFunctionService { aesDecryptFastParameters( data: string, iv: string, - mac: string, + mac: string | null, key: SymmetricCryptoKey, - ): DecryptParameters { - const p = new DecryptParameters(); + ): CbcDecryptParameters { + const p = {} as CbcDecryptParameters; if (key.meta != null) { p.encKey = key.meta.encKeyByteString; p.macKey = key.meta.macKeyByteString; @@ -275,7 +277,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService { return p; } - aesDecryptFast(parameters: DecryptParameters, mode: "cbc" | "ecb"): Promise { + aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise { const decipher = (forge as any).cipher.createDecipher( this.toWebCryptoAesMode(mode), parameters.encKey, @@ -294,21 +301,27 @@ export class WebCryptoFunctionService implements CryptoFunctionService { async aesDecrypt( data: Uint8Array, - iv: Uint8Array, + iv: Uint8Array | null, key: Uint8Array, mode: "cbc" | "ecb", ): Promise { if (mode === "ecb") { // Web crypto does not support AES-ECB mode, so we need to do this in forge. - const params = new DecryptParameters(); - params.data = this.toByteString(data); - params.encKey = this.toByteString(key); - const result = await this.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + data: this.toByteString(data), + encKey: this.toByteString(key), + }; + const result = await this.aesDecryptFast({ mode: "ecb", parameters }); return Utils.fromByteStringToArray(result); } const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [ "decrypt", ]); + + // CBC + if (iv == null) { + throw new Error("IV is required for CBC mode."); + } const buffer = await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data); return new Uint8Array(buffer); } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts index 3c8c39e21e8..61f36fa0b75 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts @@ -8,7 +8,14 @@ import { DerivedStateProvider } from "../derived-state.provider"; import { DefaultDerivedState } from "./default-derived-state"; export class DefaultDerivedStateProvider implements DerivedStateProvider { - private cache: Record> = {}; + /** + * The cache uses a WeakMap to maintain separate derived states per user. + * Each user's state Observable acts as a unique key, without needing to + * pass around `userId`. Also, when a user's state Observable is cleaned up + * (like during an account swap) their cache is automatically garbage + * collected. + */ + private cache = new WeakMap, Record>>(); constructor() {} @@ -17,8 +24,14 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { + let stateCache = this.cache.get(parentState$); + if (!stateCache) { + stateCache = {}; + this.cache.set(parentState$, stateCache); + } + const cacheKey = deriveDefinition.buildCacheKey(); - const existingDerivedState = this.cache[cacheKey]; + const existingDerivedState = stateCache[cacheKey]; if (existingDerivedState != null) { // I have to cast out of the unknown generic but this should be safe if rules // around domain token are made @@ -26,7 +39,7 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { } const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies); - this.cache[cacheKey] = newDerivedState; + stateCache[cacheKey] = newDerivedState; return newDerivedState; } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts index 7e8d76bd203..6fcc1c408cb 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts @@ -9,6 +9,7 @@ import { DeriveDefinition } from "../derive-definition"; import { StateDefinition } from "../state-definition"; import { DefaultDerivedState } from "./default-derived-state"; +import { DefaultDerivedStateProvider } from "./default-derived-state.provider"; let callCount = 0; const cleanupDelayMs = 10; @@ -182,4 +183,29 @@ describe("DefaultDerivedState", () => { expect(await firstValueFrom(observable)).toEqual(new Date(newDate)); }); }); + + describe("account switching", () => { + let provider: DefaultDerivedStateProvider; + + beforeEach(() => { + provider = new DefaultDerivedStateProvider(); + }); + + it("should provide a dedicated cache for each account", async () => { + const user1State$ = new Subject(); + const user1Derived = provider.get(user1State$, deriveDefinition, deps); + const user1Emissions = trackEmissions(user1Derived.state$); + + const user2State$ = new Subject(); + const user2Derived = provider.get(user2State$, deriveDefinition, deps); + const user2Emissions = trackEmissions(user2Derived.state$); + + user1State$.next("2015-12-30"); + user2State$.next("2020-12-29"); + await awaitAsync(); + + expect(user1Emissions).toEqual([new Date("2015-12-30")]); + expect(user2Emissions).toEqual([new Date("2020-12-29")]); + }); + }); }); diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 9e36aa69417..138c7c03318 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -8,8 +8,14 @@ import { CollectionDetailsResponse, } from "@bitwarden/admin-console/common"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction } from "../../../../auth/src/common/abstractions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "../../../../auth/src/common/types"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { InternalOrganizationServiceAbstraction } from "../../admin-console/abstractions/organization/organization.service.abstraction"; diff --git a/libs/common/src/services/notifications.service.ts b/libs/common/src/services/notifications.service.ts index 4a14332af8a..f88c904bee1 100644 --- a/libs/common/src/services/notifications.service.ts +++ b/libs/common/src/services/notifications.service.ts @@ -227,6 +227,11 @@ export class NotificationsService implements NotificationsServiceAbstraction { await this.syncService.fullSync(true); } break; + case NotificationType.SyncOrganizationCollectionSettingChanged: + if (isAuthenticated) { + await this.syncService.fullSync(true); + } + break; default: break; } diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts index 540f26bba2d..77ed6c960ab 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts @@ -6,10 +6,10 @@ import { FakeUserDecryptionOptions as UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, mockAccountServiceWith, FakeStateProvider } from "../../../spec"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; @@ -18,10 +18,12 @@ import { Policy } from "../../admin-console/models/domain/policy"; import { TokenService } from "../../auth/abstractions/token.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { LogService } from "../../platform/abstractions/log.service"; +import { Utils } from "../../platform/misc/utils"; import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION, } from "../../services/vault-timeout/vault-timeout-settings.state"; +import { UserId } from "../../types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts index 0f5bdca93da..ffc8b6e0144 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts @@ -21,6 +21,8 @@ import { } from "@bitwarden/auth/common"; import { BiometricStateService } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts index 8a166e63a1f..986bbbf95a4 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.spec.ts @@ -3,8 +3,6 @@ import { BehaviorSubject, from, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; import { BiometricsService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; @@ -15,10 +13,12 @@ import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { StateService } from "../../platform/abstractions/state.service"; import { Utils } from "../../platform/misc/utils"; +import { TaskSchedulerService } from "../../platform/scheduling"; import { StateEventRunnerService } from "../../platform/state"; import { UserId } from "../../types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index 8ab10b44b24..08dc02bb1ab 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -4,8 +4,6 @@ import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from " import { CollectionService } from "@bitwarden/admin-console/common"; import { LogoutReason } from "@bitwarden/auth/common"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { BiometricsService } from "@bitwarden/key-management"; import { SearchService } from "../../abstractions/search.service"; @@ -16,9 +14,11 @@ import { AuthService } from "../../auth/abstractions/auth.service"; import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; +import { LogService } from "../../platform/abstractions/log.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { TaskSchedulerService, ScheduledTaskNames } from "../../platform/scheduling"; import { StateEventRunnerService } from "../../platform/state"; import { UserId } from "../../types/guid"; import { CipherService } from "../../vault/abstractions/cipher.service"; diff --git a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts index 53ae94c30b2..84bcaeec608 100644 --- a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const AutofillOverlayVisibility = { Off: 0, OnButtonClick: 1, diff --git a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts index 04b4a50e0ed..9397191f54b 100644 --- a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ClearClipboardDelay = { Never: null as null, TenSeconds: 10, diff --git a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts index b465a69131a..71e5c531d50 100644 --- a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const UriMatchStrategy = { Domain: 0, Host: 1, diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts index 12257905d1c..0b60aef4917 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -1,13 +1,13 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, Subject } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { CsprngArray } from "@bitwarden/common/types/csprng"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../../types/csprng"; +import { OrganizationId, UserId } from "../../types/guid"; +import { OrgKey, UserKey } from "../../types/key"; import { OrganizationBound, UserBound } from "../dependencies"; import { KeyServiceLegacyEncryptorProvider } from "./key-service-legacy-encryptor-provider"; @@ -184,7 +184,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleUserId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `userKey$` emits a falsy value after emitting a truthy value", () => { @@ -199,7 +199,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleUserId$` emits and `userKey$` completes", () => { @@ -214,7 +214,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); @@ -445,7 +445,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleOrganizationId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `orgKeys$` emits a falsy value after emitting a truthy value", () => { @@ -466,7 +466,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.next(OrgRecords); orgKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleOrganizationId$` emits and `userKey$` completes", () => { @@ -486,7 +486,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); }); diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts index 3eee08eb6bd..d4a8dec7dc3 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts @@ -12,10 +12,10 @@ import { takeWhile, } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { KeyService } from "@bitwarden/key-management"; +import { EncryptService } from "../../platform/abstractions/encrypt.service"; +import { OrganizationId, UserId } from "../../types/guid"; import { OrganizationBound, SingleOrganizationDependency, diff --git a/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts index dda8bfe957a..9eec207e92d 100644 --- a/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts +++ b/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { OrganizationId } from "@bitwarden/common/types/guid"; - import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; /** An encryption strategy that protects a type's secrets with * organization-specific keys. This strategy is bound to a specific organization. diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.ts index 3fdc0a1da32..d3b7dae10f5 100644 --- a/libs/common/src/tools/cryptography/organization-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { OrganizationId } from "@bitwarden/common/types/guid"; - import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { OrganizationId } from "../../types/guid"; import { OrgKey } from "../../types/key"; import { DataPacker } from "../state/data-packer.abstraction"; diff --git a/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts index d63a5e908ef..6bb0e252af3 100644 --- a/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts +++ b/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; /** An encryption strategy that protects a type's secrets with * user-specific keys. This strategy is bound to a specific user. diff --git a/libs/common/src/tools/cryptography/user-key-encryptor.ts b/libs/common/src/tools/cryptography/user-key-encryptor.ts index 15a3a1b6d5c..296c33ea1dc 100644 --- a/libs/common/src/tools/cryptography/user-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { EncString } from "../../platform/models/domain/enc-string"; +import { UserId } from "../../types/guid"; import { UserKey } from "../../types/key"; import { DataPacker } from "../state/data-packer.abstraction"; diff --git a/libs/common/src/tools/dependencies.ts b/libs/common/src/tools/dependencies.ts index cdae45bc94a..c22e71cff67 100644 --- a/libs/common/src/tools/dependencies.ts +++ b/libs/common/src/tools/dependencies.ts @@ -1,7 +1,7 @@ import { Observable } from "rxjs"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { Policy } from "../admin-console/models/domain/policy"; +import { OrganizationId, UserId } from "../types/guid"; import { OrganizationEncryptor } from "./cryptography/organization-encryptor.abstraction"; import { UserEncryptor } from "./cryptography/user-encryptor.abstraction"; diff --git a/libs/common/src/tools/extension/data.ts b/libs/common/src/tools/extension/data.ts new file mode 100644 index 00000000000..cab6272a068 --- /dev/null +++ b/libs/common/src/tools/extension/data.ts @@ -0,0 +1,26 @@ +/** well-known name for a feature extensible through an extension. */ +export const Site = Object.freeze({ + forwarder: "forwarder", +} as const); + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export const Field = Object.freeze({ + token: "token", + baseUrl: "baseUrl", + domain: "domain", + prefix: "prefix", +} as const); + +/** Permission levels for metadata. */ +export const Permission = Object.freeze({ + /** unless a rule denies access, allow it. If a permission is `null` + * or `undefined` it should be treated as `Permission.default`. + */ + default: "default", + /** unless a rule allows access, deny it. */ + none: "none", + /** access is explicitly granted to use an extension. */ + allow: "allow", + /** access is explicitly prohibited for this extension. This rule overrides allow rules. */ + deny: "deny", +} as const); diff --git a/libs/common/src/tools/extension/extension-registry.abstraction.ts b/libs/common/src/tools/extension/extension-registry.abstraction.ts new file mode 100644 index 00000000000..7734c01ea50 --- /dev/null +++ b/libs/common/src/tools/extension/extension-registry.abstraction.ts @@ -0,0 +1,104 @@ +import { ExtensionSite } from "./extension-site"; +import { + ExtensionMetadata, + ExtensionSet, + ExtensionPermission, + SiteId, + SiteMetadata, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them. */ +export abstract class ExtensionRegistry { + /** Registers a site supporting extensibility. + * Each site may only be registered once. Calls after the first for + * the same SiteId have no effect. + * @param site identifies the site being extended + * @param meta configures the extension site + * @return self for method chaining. + * @remarks The registry initializes with a set of allowed sites and fields. + * `registerSite` drops a registration and trims its allowed fields to only + * those indicated in the allow list. + */ + abstract registerSite: (meta: SiteMetadata) => this; + + /** List all registered extension sites with their extension permission, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract sites: () => { site: SiteMetadata; permission?: ExtensionPermission }[]; + + /** Get a site's metadata + * @param site identifies a site registration + * @return the site's metadata or `undefined` if the site isn't registered. + */ + abstract site: (site: SiteId) => SiteMetadata | undefined; + + /** Registers a vendor providing an extension. + * Each vendor may only be registered once. Calls after the first for + * the same VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerVendor: (meta: VendorMetadata) => this; + + /** List all registered vendors with their permissions, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract vendors: () => { vendor: VendorMetadata; permission?: ExtensionPermission }[]; + + /** Get a vendor's metadata + * @param site identifies a vendor registration + * @return the vendor's metadata or `undefined` if the vendor isn't registered. + */ + abstract vendor: (vendor: VendorId) => VendorMetadata | undefined; + + /** Registers an extension provided by a vendor to an extension site. + * The vendor and site MUST be registered before the extension. + * Each extension may only be registered once. Calls after the first for + * the same SiteId and VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerExtension: (meta: ExtensionMetadata) => this; + + /** Get an extensions metadata + * @param site identifies the extension's site + * @param vendor identifies the extension's vendor + * @return the extension's metadata or `undefined` if the extension isn't registered. + */ + abstract extension: (site: SiteId, vendor: VendorId) => ExtensionMetadata | undefined; + + /** List all registered extensions and their permissions */ + abstract extensions: () => ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }>; + + /** Registers a permission. Only 1 permission can be registered for each extension set. + * Calls after the first *replace* the registered permission. + * @param set the collection of extensions affected by the permission + * @param permission the permission for the collection + * @return self for method chaining. + */ + abstract setPermission: (set: ExtensionSet, permission: ExtensionPermission) => this; + + /** Retrieves the current permission for the given extension set or `undefined` if + * a permission doesn't exist. + */ + abstract permission: (set: ExtensionSet) => ExtensionPermission | undefined; + + /** Returns all registered extension rules. */ + abstract permissions: () => { set: ExtensionSet; permission: ExtensionPermission }[]; + + /** Creates a point-in-time snapshot of the registry's contents with extension + * permissions applied for the provided SiteId. + * @param id identifies the extension site to create. + * @returns the extension site, or `undefined` if the site is not registered. + */ + abstract build: (id: SiteId) => ExtensionSite | undefined; +} diff --git a/libs/common/src/tools/extension/extension-site.ts b/libs/common/src/tools/extension/extension-site.ts new file mode 100644 index 00000000000..e8aba008493 --- /dev/null +++ b/libs/common/src/tools/extension/extension-site.ts @@ -0,0 +1,20 @@ +import { deepFreeze } from "../util"; + +import { ExtensionMetadata, SiteMetadata, VendorId } from "./type"; + +/** Describes the capabilities of an extension site. + * This type is immutable. + */ +export class ExtensionSite { + /** instantiate the extension site + * @param site describes the extension site + * @param vendors describes the available vendors + * @param extensions describes the available extensions + */ + constructor( + readonly site: Readonly, + readonly extensions: ReadonlyMap>, + ) { + deepFreeze(this); + } +} diff --git a/libs/common/src/tools/extension/factory.ts b/libs/common/src/tools/extension/factory.ts new file mode 100644 index 00000000000..10ebc77804a --- /dev/null +++ b/libs/common/src/tools/extension/factory.ts @@ -0,0 +1,24 @@ +import { DefaultFields, DefaultSites, Extension } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { VendorExtensions, Vendors } from "./vendor"; + +// FIXME: find a better way to build the registry than a hard-coded factory function + +/** Constructs the extension registry */ +export function buildExtensionRegistry() { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + for (const site of Reflect.ownKeys(Extension) as string[]) { + registry.registerSite(Extension[site]); + } + + for (const vendor of Vendors) { + registry.registerVendor(vendor); + } + + for (const extension of VendorExtensions) { + registry.registerExtension(extension); + } + + return registry; +} diff --git a/libs/common/src/tools/extension/index.ts b/libs/common/src/tools/extension/index.ts new file mode 100644 index 00000000000..e786dde4f59 --- /dev/null +++ b/libs/common/src/tools/extension/index.ts @@ -0,0 +1,12 @@ +export { Site, Field, Permission } from "./data"; +export { + SiteId, + FieldId, + VendorId, + ExtensionId, + ExtensionPermission, + SiteMetadata, + ExtensionMetadata, + VendorMetadata, +} from "./type"; +export { ExtensionSite } from "./extension-site"; diff --git a/libs/common/src/tools/extension/metadata.ts b/libs/common/src/tools/extension/metadata.ts new file mode 100644 index 00000000000..895b1d1b31f --- /dev/null +++ b/libs/common/src/tools/extension/metadata.ts @@ -0,0 +1,17 @@ +import { Field, Site, Permission } from "./data"; +import { FieldId, SiteId, SiteMetadata } from "./type"; + +export const DefaultSites: SiteId[] = Object.freeze(Object.keys(Site) as any); + +export const DefaultFields: FieldId[] = Object.freeze(Object.keys(Field) as any); + +export const Extension: Record = { + [Site.forwarder]: { + id: Site.forwarder, + availableFields: [Field.baseUrl, Field.domain, Field.prefix, Field.token], + }, +}; + +export const AllowedPermissions: ReadonlyArray = Object.freeze( + Object.values(Permission), +); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts new file mode 100644 index 00000000000..f4fe0e0ec05 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts @@ -0,0 +1,923 @@ +import { deepFreeze } from "../util"; + +import { Field, Site, Permission } from "./data"; +import { ExtensionSite } from "./extension-site"; +import { DefaultFields, DefaultSites } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { ExtensionMetadata, SiteId, SiteMetadata, VendorMetadata } from "./type"; +import { Bitwarden } from "./vendor/bitwarden"; + +// arbitrary test entities +const SomeSiteId: SiteId = Site.forwarder; + +const SomeSite: SiteMetadata = Object.freeze({ + id: SomeSiteId, + availableFields: [], +}); + +const SomeVendor = Bitwarden; +const SomeVendorId = SomeVendor.id; +const SomeExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: SomeVendor, name: "Some Product" }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://vault.bitwarden.com" }, + requestedFields: [], +}); + +const JustTrustUs: VendorMetadata = Object.freeze({ + id: "justrustus" as any, + name: "JustTrust.Us", +}); +const JustTrustUsExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: JustTrustUs }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://justrust.us" }, + requestedFields: [], +}); + +// In the following tests, not-null assertions (`!`) indicate that +// the returned object should never be null or undefined given +// the conditions defined within the test case +describe("RuntimeExtensionRegistry", () => { + describe("registerSite", () => { + it("registers an extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("interns the site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).not.toBe(SomeSite); + }); + + it("registers an extension site with fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const site: SiteMetadata = { + ...SomeSite, + availableFields: [Field.baseUrl], + }; + + const result = registry.registerSite(site).site(SomeSiteId); + + expect(result).toEqual(site); + }); + + it("ignores unavailable sites", () => { + const registry = new RuntimeExtensionRegistry([], []); + const ignored: SiteMetadata = { + id: "an-unavailable-site" as any, + availableFields: [], + }; + + const result = registry.registerSite(ignored).sites(); + + expect(result).toEqual([]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("ignores unknown available fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [SomeSite.availableFields, "ignored" as any], + }; + + const { availableFields } = registry.registerSite(ignored).site(SomeSiteId)!; + + expect(availableFields).toEqual(SomeSite.availableFields); + }); + + it("freezes the site definition", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const site = registry.registerSite(SomeSite).site(SomeSiteId)!; + + // reassigning `availableFields` throws b/c the object is frozen + expect(() => (site.availableFields = [Field.domain])).toThrow(); + }); + }); + + describe("site", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.site(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + + const first = registry.site(SomeSiteId); + const second = registry.site(SomeSiteId); + + expect(first).toBe(second); + }); + }); + + describe("sites", () => { + it("lists registered sites", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + const barSite: SiteMetadata = { + id: "bar" as any, + availableFields: [], + }; + + const result = registry.registerSite(SomeSite).registerSite(barSite).sites(); + + expect(result.some(({ site }) => site.id === SomeSiteId)).toBeTrue(); + expect(result.some(({ site }) => site.id === barSite.id)).toBeTrue(); + }); + + it("includes permissions for a site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).sites(); + + expect(result).toEqual([{ site: SomeSite }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .setPermission({ site: "bar" as any }, Permission.deny) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + }); + + describe("registerVendor", () => { + it("registers a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + const result = registry.registerVendor(SomeVendor).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor }]); + }); + + it("freezes the vendor definition", () => { + const registry = new RuntimeExtensionRegistry([], []); + // copy `SomeVendor` because it is already frozen + const original: VendorMetadata = { ...SomeVendor }; + + const [{ vendor }] = registry.registerVendor(original).vendors(); + + // reassigning `name` throws b/c the object is frozen + expect(() => (vendor.name = "Bytewarden")).toThrow(); + }); + }); + + describe("vendor", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry.vendor(SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor); + + const first = registry.vendor(SomeVendorId); + const second = registry.vendor(SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("vendors", () => { + it("lists registered vendors", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.registerVendor(SomeVendor).registerVendor(JustTrustUs); + + const result = registry.vendors(); + + expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBeTrue(); + expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBeTrue(); + }); + + it("includes permissions for a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry + .registerVendor(SomeVendor) + .setPermission({ vendor: SomeVendorId }, Permission.allow) + .vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry([], []); + const vendor: VendorMetadata = SomeVendor; + const ignored: VendorMetadata = { + ...SomeVendor, + name: "Duplicate", + }; + + const result = registry.registerVendor(vendor).registerVendor(ignored).vendors(); + + expect(result).toEqual([{ vendor }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.setPermission({ vendor: JustTrustUs.id }, Permission.deny).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + }); + + describe("setPermission", () => { + it("sets the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("ignores a site permission unless it is in the allowed sites list", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toBeUndefined(); + }); + + it("throws when a permission is invalid", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + expect(() => registry.setPermission({ all: true }, "invalid" as any)).toThrow(); + }); + + it("throws when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + expect(() => registry.setPermission(target, Permission.allow)).toThrow(); + }); + }); + + describe("permission", () => { + it("gets the default all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.default); + }); + + it("gets an all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + registry.setPermission(target, Permission.none); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.none); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("returns undefined when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + const permission = registry.permission(target); + + expect(permission).toBeUndefined(); + }); + }); + + describe("permissions", () => { + it("returns a default all permission by default", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.default }]); + }); + + it("returns the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.setPermission({ all: true }, Permission.none); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.none }]); + }); + + it("includes site permissions", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + registry.registerSite(SomeSite).setPermission({ site: SomeSiteId }, Permission.allow); + registry + .registerSite({ + id: "bar" as any, + availableFields: [], + }) + .setPermission({ site: "bar" as any }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.site === SomeSiteId && p.permission === Permission.allow), + ).toBeTrue(); + expect( + result.some((p: any) => p.set.site === "bar" && p.permission === Permission.deny), + ).toBeTrue(); + }); + + it("includes vendor permissions", () => { + const registry = new RuntimeExtensionRegistry([], DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + registry + .registerVendor(JustTrustUs) + .setPermission({ vendor: JustTrustUs.id }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.vendor === SomeVendorId && p.permission === Permission.allow), + ).toBeTrue(); + expect( + result.some( + (p: any) => p.set.vendor === JustTrustUs.id && p.permission === Permission.deny, + ), + ).toBeTrue(); + }); + }); + + describe("registerExtension", () => { + it("registers an extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("ignores extensions with nonregistered sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerVendor(SomeVendor); + + // precondition: the site is not registered + expect(registry.site(SomeSiteId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite); + + // precondition: the vendor is not registered + expect(registry.vendor(SomeVendorId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores repeated extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + // precondition: the vendor is already registered + expect(registry.extension(SomeSiteId, SomeVendorId)).toBeDefined(); + + const result = registry + .registerExtension({ + ...SomeExtension, + requestedFields: [Field.domain], + }) + .extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("interns site metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedSite = registry.site(SomeSiteId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.site).toBe(internedSite); + }); + + it("interns vendor metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedVendor = registry.vendor(SomeVendorId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.product.vendor).toBe(internedVendor); + }); + + it("freezes the extension metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const extension = registry.extension(SomeSiteId, SomeVendorId)!; + + // field assignments & mutation functions throw b/c the object is frozen + expect(() => ((extension.site as any) = SomeSite)).toThrow(); + expect(() => ((extension.product.vendor as any) = SomeVendor)).toThrow(); + expect(() => ((extension.product.name as any) = "SomeVendor")).toThrow(); + expect(() => ((extension.host as any) = {})).toThrow(); + expect(() => ((extension.host.selfHost as any) = {})).toThrow(); + expect(() => ((extension.host as any).authorization = "basic")).toThrow(); + expect(() => ((extension.host as any).baseUrl = "https://www.example.com")).toThrow(); + expect(() => ((extension.requestedFields as any) = [Field.baseUrl])).toThrow(); + expect(() => (extension.requestedFields as any).push(Field.baseUrl)).toThrow(); + }); + }); + + describe("extension", () => { + describe("extension", () => { + it("returns `undefined` for an unknown extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("interns the extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + const first = registry.extension(SomeSiteId, SomeVendorId); + const second = registry.extension(SomeSiteId, SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("extensions", () => { + it("lists registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + registry.registerVendor(SomeVendor).registerExtension(SomeExtension); + registry.registerVendor(JustTrustUs).registerExtension(JustTrustUsExtension); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === SomeVendorId, + ), + ).toBeTrue(); + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === JustTrustUs.id, + ), + ).toBeTrue(); + }); + + it("includes permissions for extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension, permissions }) => + extension.site.id === SomeSiteId && + extension.product.vendor.id === SomeVendorId && + permissions.includes(Permission.allow), + ), + ).toBeTrue(); + }); + }); + + describe("build", () => { + it("builds an empty extension site when no extensions are registered", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.build(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + describe("when the all permission is `default`", () => { + const allPermission = Permission.default; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `none`", () => { + const allPermission = Permission.none; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.none); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.allow]])("includes sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.allow]])("includes vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `allow`", () => { + const allPermission = Permission.allow; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + }); + + describe("when the all permission is `deny`", () => { + const allPermission = Permission.deny; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.deny); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + }); + }); +}); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.ts b/libs/common/src/tools/extension/runtime-extension-registry.ts new file mode 100644 index 00000000000..1c630dcc915 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.ts @@ -0,0 +1,286 @@ +import { deepFreeze } from "../util"; + +import { ExtensionRegistry } from "./extension-registry.abstraction"; +import { ExtensionSite } from "./extension-site"; +import { AllowedPermissions } from "./metadata"; +import { + ExtensionMetadata, + ExtensionPermission, + ExtensionSet, + FieldId, + ProductMetadata, + SiteMetadata, + SiteId, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them in application memory. */ +export class RuntimeExtensionRegistry implements ExtensionRegistry { + /** Instantiates the extension registry + * @param allowedSites sites that are valid for use by any extension; + * this is most useful to disable an extension site that is only + * available on a specific client. + * @param allowedFields fields that are valid for use by any extension; + * this is most useful to prohibit access to a field via policy. + */ + constructor( + private readonly allowedSites: SiteId[], + private readonly allowedFields: FieldId[], + ) { + Object.freeze(this.allowedFields); + Object.freeze(this.allowedSites); + } + + private allPermission: ExtensionPermission = "default"; + + private siteRegistrations = new Map(); + private sitePermissions = new Map(); + + private vendorRegistrations = new Map(); + private vendorPermissions = new Map(); + + private extensionRegistrations = new Array(); + private extensionsBySiteByVendor = new Map>(); + + registerSite(site: SiteMetadata): this { + if (!this.allowedSites.includes(site.id)) { + return this; + } + + // verify requested fields are on the list of valid fields to expose to + // an extension + const availableFields = site.availableFields.filter((field) => + this.allowedFields.includes(field), + ); + const validated: SiteMetadata = deepFreeze({ id: site.id, availableFields }); + + if (!this.siteRegistrations.has(site.id)) { + this.siteRegistrations.set(site.id, validated); + } + + return this; + } + + site(site: SiteId): SiteMetadata | undefined { + const result = this.siteRegistrations.get(site); + return result; + } + + sites() { + const sites: { site: SiteMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, site] of this.siteRegistrations.entries()) { + const s: (typeof sites)[number] = { site }; + const permission = this.sitePermissions.get(k); + if (permission) { + s.permission = permission; + } + + sites.push(s); + } + + return sites; + } + + registerVendor(vendor: VendorMetadata): this { + if (!this.vendorRegistrations.has(vendor.id)) { + const frozen = deepFreeze(vendor); + this.vendorRegistrations.set(vendor.id, frozen); + } + + return this; + } + + vendor(vendor: VendorId): VendorMetadata | undefined { + const result = this.vendorRegistrations.get(vendor); + return result; + } + + vendors() { + const vendors: { vendor: VendorMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, vendor] of this.vendorRegistrations.entries()) { + const s: (typeof vendors)[number] = { vendor }; + const permission = this.vendorPermissions.get(k); + if (permission) { + s.permission = permission; + } + + vendors.push(s); + } + + return vendors; + } + + setPermission(set: ExtensionSet, permission: ExtensionPermission): this { + if (!AllowedPermissions.includes(permission)) { + throw new Error(`invalid extension permission: ${permission}`); + } + + if ("all" in set && set.all) { + this.allPermission = permission; + } else if ("vendor" in set) { + this.vendorPermissions.set(set.vendor, permission); + } else if ("site" in set) { + if (this.allowedSites.includes(set.site)) { + this.sitePermissions.set(set.site, permission); + } + } else { + throw new Error(`Unrecognized extension set received: ${JSON.stringify(set)}.`); + } + + return this; + } + + permission(set: ExtensionSet) { + if ("all" in set && set.all) { + return this.allPermission; + } else if ("vendor" in set) { + return this.vendorPermissions.get(set.vendor); + } else if ("site" in set) { + return this.sitePermissions.get(set.site); + } else { + return undefined; + } + } + + permissions() { + const rules: { set: ExtensionSet; permission: ExtensionPermission }[] = []; + rules.push({ set: { all: true }, permission: this.allPermission }); + + for (const [site, permission] of this.sitePermissions.entries()) { + rules.push({ set: { site }, permission }); + } + + for (const [vendor, permission] of this.vendorPermissions.entries()) { + rules.push({ set: { vendor }, permission }); + } + + return rules; + } + + registerExtension(meta: ExtensionMetadata): this { + const site = this.siteRegistrations.get(meta.site.id); + const vendor = this.vendorRegistrations.get(meta.product.vendor.id); + if (!site || !vendor) { + return this; + } + + // exit early if the extension is already registered + const extensionsByVendor = + this.extensionsBySiteByVendor.get(meta.site.id) ?? new Map(); + if (extensionsByVendor.has(meta.product.vendor.id)) { + return this; + } + + // create immutable copy; this updates the vendor and site with + // their internalized representation to provide reference equality + // across registrations + const product: ProductMetadata = { vendor }; + if (meta.product.name) { + product.name = meta.product.name; + } + const extension: ExtensionMetadata = Object.freeze({ + site, + product: Object.freeze(product), + host: Object.freeze({ ...meta.host }), + requestedFields: Object.freeze([...meta.requestedFields]), + }); + + // register it + const index = this.extensionRegistrations.push(extension) - 1; + extensionsByVendor.set(vendor.id, index); + this.extensionsBySiteByVendor.set(site.id, extensionsByVendor); + + return this; + } + + extension(site: SiteId, vendor: VendorId): ExtensionMetadata | undefined { + const index = this.extensionsBySiteByVendor.get(site)?.get(vendor) ?? -1; + if (index < 0) { + return undefined; + } else { + return this.extensionRegistrations[index]; + } + } + + private getPermissions(site: SiteId, vendor: VendorId): ExtensionPermission[] { + const permissions = [ + this.sitePermissions.get(site), + this.vendorPermissions.get(vendor), + this.allPermission, + // Need to cast away `undefined` because typescript isn't + // aware that the filter eliminates undefined elements + ].filter((p) => !!p) as ExtensionPermission[]; + + return permissions; + } + + extensions(): ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }> { + const extensions = []; + for (const extension of this.extensionRegistrations) { + const permissions = this.getPermissions(extension.site.id, extension.product.vendor.id); + + extensions.push({ extension, permissions }); + } + + return extensions; + } + + build(id: SiteId): ExtensionSite | undefined { + const site = this.siteRegistrations.get(id); + if (!site) { + return undefined; + } + + if (this.allPermission === "deny") { + return new ExtensionSite(site, new Map()); + } + + const extensions = new Map(); + const entries = this.extensionsBySiteByVendor.get(id)?.entries() ?? ([] as const); + for (const [vendor, index] of entries) { + const permissions = this.getPermissions(id, vendor); + + const extension = evaluate(permissions, this.extensionRegistrations[index]); + if (extension) { + extensions.set(vendor, extension); + } + } + + const extensionSite = new ExtensionSite(site, extensions); + return extensionSite; + } +} + +function evaluate( + permissions: ExtensionPermission[], + value: ExtensionMetadata, +): ExtensionMetadata | undefined { + // deny always wins + if (permissions.includes("deny")) { + return undefined; + } + + // allow overrides implicit permissions + if (permissions.includes("allow")) { + return value; + } + + // none permission becomes a deny + if (permissions.includes("none")) { + return undefined; + } + + // default permission becomes an allow + if (permissions.includes("default")) { + return value; + } + + // if no permission is recognized, throw. This code is unreachable. + throw new Error("failed to recognize any permissions"); +} diff --git a/libs/common/src/tools/extension/type.ts b/libs/common/src/tools/extension/type.ts new file mode 100644 index 00000000000..f37d4ff8e53 --- /dev/null +++ b/libs/common/src/tools/extension/type.ts @@ -0,0 +1,109 @@ +import { Opaque } from "type-fest"; + +import { Site, Field, Permission } from "./data"; + +/** well-known name for a feature extensible through an extension. */ +export type SiteId = keyof typeof Site; + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export type FieldId = keyof typeof Field; + +/** Identifies a vendor extending bitwarden */ +export type VendorId = Opaque<"vendor", string>; + +/** uniquely identifies an extension. */ +export type ExtensionId = { site: SiteId; vendor: VendorId }; + +/** Permission levels for metadata. */ +export type ExtensionPermission = keyof typeof Permission; + +/** The capabilities and descriptive content for an extension */ +export type SiteMetadata = { + /** Uniquely identifies the extension site. */ + id: SiteId; + + /** Lists the fields disclosed by the extension to the vendor */ + availableFields: FieldId[]; +}; + +/** The capabilities and descriptive content for an extension */ +export type VendorMetadata = { + /** Uniquely identifies the vendor. */ + id: VendorId; + + /** Brand name of the service providing the extension. */ + name: string; +}; + +type TokenHeader = + | { + /** Transmit the token as the value of an `Authentication` header */ + authentication: true; + } + | { + /** Transmit the token as an `Authorization` header and a formatted value + * * `bearer` uses OAUTH-2.0 bearer token format + * * `token` prefixes the token with "Token" + * * `basic-username` uses HTTP Basic authentication format, encoding the + * token as the username. + */ + authorization: "bearer" | "token" | "basic-username"; + }; + +/** Catalogues an extension's hosting status. + * selfHost: "never" always uses the service's base URL + * selfHost: "maybe" allows the user to override the service's + * base URL with their own. + * selfHost: "always" requires a base URL. + */ +export type ApiHost = TokenHeader & + ( + | { selfHost: "never"; baseUrl: string } + | { selfHost: "maybe"; baseUrl: string } + | { selfHost: "always" } + ); + +/** Describes a branded product */ +export type ProductMetadata = { + /** The vendor providing the extension */ + vendor: VendorMetadata; + + /** The branded name of the product, if it varies from the Vendor name */ + name?: string; +}; + +/** Describes an extension provided by a vendor */ +export type ExtensionMetadata = { + /** The part of Bitwarden extended by the vendor's services */ + readonly site: Readonly; + + /** Product description */ + readonly product: Readonly; + + /** Hosting provider capabilities required by the extension */ + readonly host: Readonly; + + /** Lists the fields disclosed by the extension to the vendor. + * This should be a subset of the `availableFields` listed in + * the extension. + */ + readonly requestedFields: ReadonlyArray>; +}; + +/** Identifies a collection of extensions. + */ +export type ExtensionSet = + | { + /** A set of extensions sharing an extension point */ + site: SiteId; + } + | { + /** A set of extensions sharing a vendor */ + vendor: VendorId; + } + | { + /** The total set of extensions. This is used to set a categorical + * rule affecting all extensions. + */ + all: true; + }; diff --git a/libs/common/src/tools/extension/vendor/addyio.ts b/libs/common/src/tools/extension/vendor/addyio.ts new file mode 100644 index 00000000000..c33abd570ad --- /dev/null +++ b/libs/common/src/tools/extension/vendor/addyio.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const AddyIo: VendorMetadata = { + id: Vendor.addyio, + name: "Addy.io", +}; + +export const AddyIoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: AddyIo, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://app.addy.io", + }, + requestedFields: [Field.token, Field.baseUrl, Field.domain], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/bitwarden.ts b/libs/common/src/tools/extension/vendor/bitwarden.ts new file mode 100644 index 00000000000..7f659c2d07f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/bitwarden.ts @@ -0,0 +1,8 @@ +import { VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Bitwarden: VendorMetadata = Object.freeze({ + id: Vendor.bitwarden, + name: "Bitwarden", +}); diff --git a/libs/common/src/tools/extension/vendor/data.ts b/libs/common/src/tools/extension/vendor/data.ts new file mode 100644 index 00000000000..7f0802ef82f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/data.ts @@ -0,0 +1,11 @@ +import { VendorId } from "../type"; + +export const Vendor = Object.freeze({ + addyio: "addyio" as VendorId, + bitwarden: "bitwarden" as VendorId, // RESERVED + duckduckgo: "duckduckgo" as VendorId, + fastmail: "fastmail" as VendorId, + forwardemail: "forwardemail" as VendorId, + mozilla: "mozilla" as VendorId, + simplelogin: "simplelogin" as VendorId, +} as const); diff --git a/libs/common/src/tools/extension/vendor/duckduckgo.ts b/libs/common/src/tools/extension/vendor/duckduckgo.ts new file mode 100644 index 00000000000..ca4634192f5 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/duckduckgo.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const DuckDuckGo: VendorMetadata = { + id: Vendor.duckduckgo, + name: "DuckDuckGo", +}; + +export const DuckDuckGoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: DuckDuckGo, + }, + host: { + authorization: "bearer", + selfHost: "never", + baseUrl: "https://quack.duckduckgo.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/fastmail.ts b/libs/common/src/tools/extension/vendor/fastmail.ts new file mode 100644 index 00000000000..e6fb9ec16be --- /dev/null +++ b/libs/common/src/tools/extension/vendor/fastmail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Fastmail: VendorMetadata = { + id: Vendor.fastmail, + name: "Fastmail", +}; + +export const FastmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Fastmail, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://api.fastmail.com", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/forwardemail.ts b/libs/common/src/tools/extension/vendor/forwardemail.ts new file mode 100644 index 00000000000..4fbc8c139b1 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/forwardemail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const ForwardEmail: VendorMetadata = { + id: Vendor.forwardemail, + name: "Forward Email", +}; + +export const ForwardEmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: ForwardEmail, + }, + host: { + authorization: "basic-username", + selfHost: "never", + baseUrl: "https://api.forwardemail.net", + }, + requestedFields: [Field.domain, Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/index.ts b/libs/common/src/tools/extension/vendor/index.ts new file mode 100644 index 00000000000..3bac78c80db --- /dev/null +++ b/libs/common/src/tools/extension/vendor/index.ts @@ -0,0 +1,30 @@ +import { deepFreeze } from "../../util"; + +import { AddyIo, AddyIoExtensions } from "./addyio"; +import { Bitwarden } from "./bitwarden"; +import { DuckDuckGo, DuckDuckGoExtensions } from "./duckduckgo"; +import { Fastmail, FastmailExtensions } from "./fastmail"; +import { ForwardEmail, ForwardEmailExtensions } from "./forwardemail"; +import { Mozilla, MozillaExtensions } from "./mozilla"; +import { SimpleLogin, SimpleLoginExtensions } from "./simplelogin"; + +export const Vendors = deepFreeze([ + AddyIo, + Bitwarden, + DuckDuckGo, + Fastmail, + ForwardEmail, + Mozilla, + SimpleLogin, +]); + +export const VendorExtensions = deepFreeze( + [ + AddyIoExtensions, + DuckDuckGoExtensions, + FastmailExtensions, + ForwardEmailExtensions, + MozillaExtensions, + SimpleLoginExtensions, + ].flat(), +); diff --git a/libs/common/src/tools/extension/vendor/mozilla.ts b/libs/common/src/tools/extension/vendor/mozilla.ts new file mode 100644 index 00000000000..b02b97d8777 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/mozilla.ts @@ -0,0 +1,26 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Mozilla: VendorMetadata = { + id: Vendor.mozilla, + name: "Mozilla", +}; + +export const MozillaExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Mozilla, + name: "Firefox Relay", + }, + host: { + authorization: "token", + selfHost: "never", + baseUrl: "https://relay.firefox.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/readme.md b/libs/common/src/tools/extension/vendor/readme.md new file mode 100644 index 00000000000..507769edd4e --- /dev/null +++ b/libs/common/src/tools/extension/vendor/readme.md @@ -0,0 +1,33 @@ +# Vendors + +This folder contains vendor-specific logic that extends the +Bitwarden password manager. + +## Vendor IDs + +A vendor's ID is used to identify and trace the code provided by +a vendor across Bitwarden. There are a few rules that vendor ids +must follow: + +1. They should be human-readable. (No UUIDs.) +2. They may only contain lowercase ASCII characters and numbers. +3. They must retain backwards compatibility with prior versions. + +As such, any given ID may not not match the vendor's present +brand identity. Said branding may be stored in `VendorMetadata.name`. + +## Core files + +There are 4 vendor-independent files in this directory. + +- `data.ts` - core metadata used for system initialization +- `index.ts` - exports vendor metadata +- `README.md` - this file + +## Vendor definitions + +Each vendor should have one and only one definition, whose name +MUST match their `VendorId`. The vendor is free to use either a +single file (e.g. `bitwarden.ts`) or a folder containing multiple +files (e.g. `bitwarden/extension.ts`, `bitwarden/forwarder.ts`) to +host their files. diff --git a/libs/common/src/tools/extension/vendor/simplelogin.ts b/libs/common/src/tools/extension/vendor/simplelogin.ts new file mode 100644 index 00000000000..21ee969cebb --- /dev/null +++ b/libs/common/src/tools/extension/vendor/simplelogin.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const SimpleLogin: VendorMetadata = { + id: Vendor.simplelogin, + name: "SimpleLogin", +}; + +export const SimpleLoginExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: SimpleLogin, + }, + host: { + authentication: true, + selfHost: "maybe", + baseUrl: "https://app.simplelogin.io", + }, + requestedFields: [Field.baseUrl, Field.token, Field.domain], + }, +]; diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts index 58115c783c7..42581c08dee 100644 --- a/libs/common/src/tools/integration/integration-context.spec.ts +++ b/libs/common/src/tools/integration/integration-context.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nService } from "../../platform/abstractions/i18n.service"; import { IntegrationContext } from "./integration-context"; import { IntegrationId } from "./integration-id"; diff --git a/libs/common/src/tools/integration/integration-context.ts b/libs/common/src/tools/integration/integration-context.ts index f30810ff81a..40648df6803 100644 --- a/libs/common/src/tools/integration/integration-context.ts +++ b/libs/common/src/tools/integration/integration-context.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { I18nService } from "../../platform/abstractions/i18n.service"; +import { Utils } from "../../platform/misc/utils"; import { IntegrationMetadata } from "./integration-metadata"; import { ApiSettings, IntegrationRequest } from "./rpc"; diff --git a/libs/common/src/tools/integration/rpc/rest-client.spec.ts b/libs/common/src/tools/integration/rpc/rest-client.spec.ts index e113ab9ff43..9b76d305e6f 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.spec.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.spec.ts @@ -1,7 +1,7 @@ import { mock } from "jest-mock-extended"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { RestClient } from "./rest-client"; diff --git a/libs/common/src/tools/integration/rpc/rest-client.ts b/libs/common/src/tools/integration/rpc/rest-client.ts index 287bb4f2573..c42244166e7 100644 --- a/libs/common/src/tools/integration/rpc/rest-client.ts +++ b/libs/common/src/tools/integration/rpc/rest-client.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ApiService } from "../../../abstractions/api.service"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; import { IntegrationRequest } from "./integration-request"; import { JsonRpc } from "./rpc"; diff --git a/libs/common/src/tools/private-classifier.ts b/libs/common/src/tools/private-classifier.ts index de21f78f1c6..58244ae9906 100644 --- a/libs/common/src/tools/private-classifier.ts +++ b/libs/common/src/tools/private-classifier.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PrivateClassifier implements Classifier, Data> { constructor(private keys: (keyof Jsonify)[] = undefined) {} diff --git a/libs/common/src/tools/public-classifier.ts b/libs/common/src/tools/public-classifier.ts index e7c8c24ba78..e036ebd1c42 100644 --- a/libs/common/src/tools/public-classifier.ts +++ b/libs/common/src/tools/public-classifier.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { Classifier } from "@bitwarden/common/tools/state/classifier"; +import { Classifier } from "./state/classifier"; export class PublicClassifier implements Classifier> { constructor(private keys: (keyof Jsonify)[]) {} diff --git a/libs/common/src/tools/rx.spec.ts b/libs/common/src/tools/rx.spec.ts index 9ce147a3ff4..2c433fef93b 100644 --- a/libs/common/src/tools/rx.spec.ts +++ b/libs/common/src/tools/rx.spec.ts @@ -56,7 +56,7 @@ describe("errorOnChange", () => { source$.complete(); - expect(complete).toBeTrue(); + expect(complete).toBe(true); }); it("errors when the input changes", async () => { diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index 74c0e77b394..fcc273d41bb 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,12 +1,12 @@ import { mock } from "jest-mock-extended"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc } from "../../../../../spec"; import { EncryptService } from "../../../../platform/abstractions/encrypt.service"; +import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../../platform/services/container.service"; +import { UserKey } from "../../../../types/key"; import { SendType } from "../../enums/send-type"; import { SendData } from "../data/send.data"; @@ -123,7 +123,12 @@ describe("Send", () => { const view = await send.decrypt(); expect(text.decrypt).toHaveBeenNthCalledWith(1, "cryptoKey"); - expect(send.name.decrypt).toHaveBeenNthCalledWith(1, null, "cryptoKey"); + expect(send.name.decrypt).toHaveBeenNthCalledWith( + 1, + null, + "cryptoKey", + "Property: name; ObjectContext: No Domain Context", + ); expect(view).toMatchObject({ id: "id", diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 43115b65937..c2390d439e7 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -81,6 +81,8 @@ export class Send extends Domain { const sendKeyEncryptionKey = await keyService.getUserKey(); model.key = await encryptService.decryptToBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/tools/send/services/send-api.service.abstraction.ts b/libs/common/src/tools/send/services/send-api.service.abstraction.ts index a6427824a64..570f3e746a0 100644 --- a/libs/common/src/tools/send/services/send-api.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send-api.service.abstraction.ts @@ -22,11 +22,6 @@ export abstract class SendApiService { postSend: (request: SendRequest) => Promise; postFileTypeSend: (request: SendRequest) => Promise; postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postSendFileLegacy: (data: FormData) => Promise; putSend: (id: string, request: SendRequest) => Promise; putSendRemovePassword: (id: string) => Promise; deleteSend: (id: string) => Promise; diff --git a/libs/common/src/tools/send/services/send-api.service.ts b/libs/common/src/tools/send/services/send-api.service.ts index ff71408bce3..f709553646f 100644 --- a/libs/common/src/tools/send/services/send-api.service.ts +++ b/libs/common/src/tools/send/services/send-api.service.ts @@ -5,7 +5,6 @@ import { FileUploadApiMethods, FileUploadService, } from "../../../platform/abstractions/file-upload/file-upload.service"; -import { Utils } from "../../../platform/misc/utils"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { SendType } from "../enums/send-type"; import { SendData } from "../models/data/send.data"; @@ -106,15 +105,6 @@ export class SendApiService implements SendApiServiceAbstraction { return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); } - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postSendFileLegacy(data: FormData): Promise { - const r = await this.apiService.send("POST", "/sends/file", data, true, true); - return new SendResponse(r); - } - async putSend(id: string, request: SendRequest): Promise { const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true); return new SendResponse(r); @@ -173,9 +163,7 @@ export class SendApiService implements SendApiServiceAbstraction { this.generateMethods(uploadDataResponse, response), ); } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - response = await this.legacyServerSendFileUpload(sendData, request); - } else if (e instanceof ErrorResponse) { + if (e instanceof ErrorResponse) { throw new Error((e as ErrorResponse).getSingleMessage()); } else { throw e; @@ -219,35 +207,4 @@ export class SendApiService implements SendApiServiceAbstraction { return this.deleteSend(sendId); }; } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerSendFileUpload( - sendData: [Send, EncArrayBuffer], - request: SendRequest, - ): Promise { - const fd = new FormData(); - try { - const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); - fd.append("model", JSON.stringify(request)); - fd.append("data", blob, sendData[0].file.fileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("model", JSON.stringify(request)); - fd.append( - "data", - Buffer.from(sendData[1].buffer) as any, - { - filepath: sendData[0].file.fileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - return await this.postSendFileLegacy(fd); - } } diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 5aca3a4b5c9..662fee02bbf 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -1,10 +1,8 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; +import { KeyService } from "@bitwarden/key-management"; -import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { FakeAccountService, FakeActiveUserState, @@ -13,12 +11,14 @@ import { mockAccountServiceWith, } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; +import { EnvironmentService } from "../../../platform/abstractions/environment.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; import { Utils } from "../../../platform/misc/utils"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../platform/services/container.service"; +import { SelfHostedEnvironment } from "../../../platform/services/default-environment.service"; import { UserId } from "../../../types/guid"; import { UserKey } from "../../../types/key"; import { SendType } from "../enums/send-type"; diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts index 6a50a1dd668..8111f6f9f17 100644 --- a/libs/common/src/tools/state/user-state-subject.spec.ts +++ b/libs/common/src/tools/state/user-state-subject.spec.ts @@ -1,9 +1,8 @@ import { BehaviorSubject, of, Subject } from "rxjs"; -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - import { awaitAsync, FakeSingleUserState, ObservableTracker } from "../../../spec"; +import { GENERATOR_DISK, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { UserBound } from "../dependencies"; import { PrivateClassifier } from "../private-classifier"; diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts index d508cf71663..c00560304e4 100644 --- a/libs/common/src/tools/state/user-state-subject.ts +++ b/libs/common/src/tools/state/user-state-subject.ts @@ -26,10 +26,9 @@ import { skip, } from "rxjs"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SingleUserState, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - +import { EncString } from "../../platform/models/domain/enc-string"; +import { SingleUserState, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; import { UserEncryptor } from "../cryptography/user-encryptor.abstraction"; import { UserBound } from "../dependencies"; import { anyComplete, errorOnChange, ready, withLatestReady } from "../rx"; diff --git a/libs/common/src/tools/util.ts b/libs/common/src/tools/util.ts new file mode 100644 index 00000000000..9a3a14c1c83 --- /dev/null +++ b/libs/common/src/tools/util.ts @@ -0,0 +1,19 @@ +/** Recursively freeze an object's own keys + * @param value the value to freeze + * @returns `value` + * @remarks this function is derived from MDN's `deepFreeze`, which + * has been committed to the public domain. + */ +export function deepFreeze(value: T): Readonly { + const keys = Reflect.ownKeys(value) as (keyof T)[]; + + for (const key of keys) { + const own = value[key]; + + if ((own && typeof own === "object") || typeof own === "function") { + deepFreeze(own); + } + } + + return Object.freeze(value); +} diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 068b2fc52d9..88cd476606d 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -2,7 +2,6 @@ // @ts-strict-ignore import { Observable } from "rxjs"; -import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; @@ -11,6 +10,7 @@ import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid import { UserKey } from "../../types/key"; import { CipherType } from "../enums/cipher-type"; import { CipherData } from "../models/data/cipher.data"; +import { LocalData } from "../models/data/local.data"; import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherWithIdRequest } from "../models/request/cipher-with-id.request"; diff --git a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts index 859f2183edb..b95dd35e5ec 100644 --- a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { UserId } from "@bitwarden/common/types/guid"; +import { UserId } from "../../../types/guid"; import { Folder } from "../../models/domain/folder"; import { FolderResponse } from "../../models/response/folder.response"; diff --git a/libs/common/src/vault/icon/build-cipher-icon.ts b/libs/common/src/vault/icon/build-cipher-icon.ts index 78e6ecd7b4f..5775bc7f55e 100644 --- a/libs/common/src/vault/icon/build-cipher-icon.ts +++ b/libs/common/src/vault/icon/build-cipher-icon.ts @@ -43,10 +43,18 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show isWebsite = hostnameUri.indexOf("http") === 0 && hostnameUri.indexOf(".") > -1; } + if (isWebsite && (hostnameUri.endsWith(".onion") || hostnameUri.endsWith(".i2p"))) { + image = null; + fallbackImage = "images/bwi-globe.png"; + break; + } + if (showFavicon && isWebsite) { try { image = `${iconsServerUrl}/${Utils.getHostname(hostnameUri)}/icon.png`; fallbackImage = "images/bwi-globe.png"; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error since the fallback icon will be shown if image is null. } diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index 14dec8dea0c..8cae7170738 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptService } from "../../../platform/abstractions/encrypt.service"; @@ -101,7 +103,7 @@ describe("Attachment", () => { it("uses the provided key without depending on KeyService", async () => { const providedKey = mock(); - await attachment.decrypt(null, providedKey); + await attachment.decrypt(null, "", providedKey); expect(keyService.getUserKeyWithLegacySupport).not.toHaveBeenCalled(); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey); @@ -111,7 +113,7 @@ describe("Attachment", () => { const orgKey = mock(); keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); - await attachment.decrypt("orgId", null); + await attachment.decrypt("orgId", "", null); expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId"); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey); @@ -121,7 +123,7 @@ describe("Attachment", () => { const userKey = mock(); keyService.getUserKeyWithLegacySupport.mockResolvedValue(userKey); - await attachment.decrypt(null, null); + await attachment.decrypt(null, "", null); expect(keyService.getUserKeyWithLegacySupport).toHaveBeenCalled(); expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey); diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 1178f441c5e..4eee0307746 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -38,7 +38,11 @@ export class Attachment extends Domain { ); } - async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { const view = await this.decryptObj( new AttachmentView(this), { @@ -46,6 +50,7 @@ export class Attachment extends Domain { }, orgId, encKey, + "DomainType: Attachment; " + context, ); if (this.key != null) { @@ -64,6 +69,8 @@ export class Attachment extends Domain { const encryptService = Utils.getContainerService().getEncryptService(); const decValue = await encryptService.decryptToBytes(this.key, encKey); return new SymmetricCryptoKey(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/vault/models/domain/card.ts b/libs/common/src/vault/models/domain/card.ts index 739cbf78465..fccfe3f595b 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -37,7 +37,11 @@ export class Card extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new CardView(), { @@ -50,6 +54,7 @@ export class Card extends Domain { }, orgId, encKey, + "DomainType: Card; " + context, ); } diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 509a17a8a0e..dd79da3086e 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -1,8 +1,8 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; -import { UserId } from "@bitwarden/common/types/guid"; - +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; @@ -10,6 +10,7 @@ import { EncryptService } from "../../../platform/abstractions/encrypt.service"; import { EncString } from "../../../platform/models/domain/enc-string"; import { ContainerService } from "../../../platform/services/container.service"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; +import { UserId } from "../../../types/guid"; import { CipherService } from "../../abstractions/cipher.service"; import { FieldType, SecureNoteType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 437afe2e938..d82f4585e65 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -136,7 +136,11 @@ export class Cipher extends Domain implements Decryptable { if (this.key != null) { const encryptService = Utils.getContainerService().getEncryptService(); - const keyBytes = await encryptService.decryptToBytes(this.key, encKey); + const keyBytes = await encryptService.decryptToBytes( + this.key, + encKey, + `Cipher Id: ${this.id}; Content: CipherKey; IsEncryptedByOrgKey: ${this.organizationId != null}`, + ); if (keyBytes == null) { model.name = "[error: cannot decrypt]"; model.decryptionFailure = true; @@ -158,19 +162,36 @@ export class Cipher extends Domain implements Decryptable { switch (this.type) { case CipherType.Login: - model.login = await this.login.decrypt(this.organizationId, bypassValidation, encKey); + model.login = await this.login.decrypt( + this.organizationId, + bypassValidation, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.SecureNote: - model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey); + model.secureNote = await this.secureNote.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.Card: - model.card = await this.card.decrypt(this.organizationId, encKey); + model.card = await this.card.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); break; case CipherType.Identity: - model.identity = await this.identity.decrypt(this.organizationId, encKey); + model.identity = await this.identity.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; case CipherType.SshKey: - model.sshKey = await this.sshKey.decrypt(this.organizationId, encKey); + model.sshKey = await this.sshKey.decrypt( + this.organizationId, + `Cipher Id: ${this.id}`, + encKey, + ); break; default: break; @@ -181,7 +202,7 @@ export class Cipher extends Domain implements Decryptable { await this.attachments.reduce((promise, attachment) => { return promise .then(() => { - return attachment.decrypt(this.organizationId, encKey); + return attachment.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey); }) .then((decAttachment) => { attachments.push(decAttachment); diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index e2b7aef52f0..570e6c0b4d5 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -61,7 +61,11 @@ export class Identity extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context: string = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new IdentityView(), { @@ -86,6 +90,7 @@ export class Identity extends Domain { }, orgId, encKey, + "DomainType: Identity; " + context, ); } diff --git a/libs/common/src/vault/models/domain/login-uri.ts b/libs/common/src/vault/models/domain/login-uri.ts index 0d7380e034d..36782a81502 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -33,7 +33,11 @@ export class LoginUri extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context: string = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new LoginUriView(this), { @@ -41,6 +45,7 @@ export class LoginUri extends Domain { }, orgId, encKey, + context, ); } diff --git a/libs/common/src/vault/models/domain/login.ts b/libs/common/src/vault/models/domain/login.ts index a0a61a9b857..f9a85cd818e 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -55,6 +55,7 @@ export class Login extends Domain { async decrypt( orgId: string, bypassValidation: boolean, + context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, ): Promise { const view = await this.decryptObj( @@ -66,6 +67,7 @@ export class Login extends Domain { }, orgId, encKey, + `DomainType: Login; ${context}`, ); if (this.uris != null) { @@ -76,7 +78,7 @@ export class Login extends Domain { continue; } - const uri = await this.uris[i].decrypt(orgId, encKey); + const uri = await this.uris[i].decrypt(orgId, context, encKey); // URIs are shared remotely after decryption // we need to validate that the string hasn't been changed by a compromised server // This validation is tied to the existence of cypher.key for backwards compatibility diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index 4c4f465654e..48063f495f0 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -32,6 +32,7 @@ export class Password extends Domain { }, orgId, encKey, + "DomainType: PasswordHistory", ); } diff --git a/libs/common/src/vault/models/domain/secure-note.ts b/libs/common/src/vault/models/domain/secure-note.ts index 4769ad062d9..693ae38d9fb 100644 --- a/libs/common/src/vault/models/domain/secure-note.ts +++ b/libs/common/src/vault/models/domain/secure-note.ts @@ -20,8 +20,12 @@ export class SecureNote extends Domain { this.type = obj.type; } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { - return Promise.resolve(new SecureNoteView(this)); + async decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { + return new SecureNoteView(this); } toSecureNoteData(): SecureNoteData { diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index 3a79c1f0022..b4df172e543 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -2,9 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; - import Domain from "../../../platform/models/domain/domain-base"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SshKeyData } from "../data/ssh-key.data"; import { SshKeyView } from "../view/ssh-key.view"; @@ -32,7 +31,11 @@ export class SshKey extends Domain { ); } - decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { + decrypt( + orgId: string, + context = "No Cipher Context", + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( new SshKeyView(), { @@ -42,6 +45,7 @@ export class SshKey extends Domain { }, orgId, encKey, + "DomainType: SshKey; " + context, ); } diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index c5de01293ee..20dbd23065c 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -155,6 +155,8 @@ export class CipherView implements View, InitializerMetadata { return null; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars const item = this.item; return this.item[linkedFieldOption.propertyKey as keyof typeof item]; } diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index a6b7a0c0a22..315adb87c75 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -142,6 +142,8 @@ export class LoginUriView implements View { try { const regex = new RegExp(this.uri, "i"); return regex.test(targetUri); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Invalid regex return false; diff --git a/libs/common/src/vault/services/cipher-authorization.service.spec.ts b/libs/common/src/vault/services/cipher-authorization.service.spec.ts index cccd29ad697..15e8b03bfad 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.spec.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.spec.ts @@ -2,10 +2,10 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "../../admin-console/models/domain/organization"; +import { CollectionId } from "../../types/guid"; import { CipherView } from "../models/view/cipher.view"; import { diff --git a/libs/common/src/vault/services/cipher-authorization.service.ts b/libs/common/src/vault/services/cipher-authorization.service.ts index 260d1eeece7..025d6b1cdc3 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.ts @@ -3,9 +3,9 @@ import { map, Observable, of, shareReplay, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { CollectionId } from "../../types/guid"; import { Cipher } from "../models/domain/cipher"; import { CipherView } from "../models/view/cipher.view"; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index e6ea762c125..0d6578f165d 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -1,8 +1,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, map, of } from "rxjs"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; - +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CipherDecryptionKeys, KeyService, @@ -15,6 +15,7 @@ import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; import { UriMatchStrategy } from "../../models/domain/domain-service"; +import { BulkEncryptService } from "../../platform/abstractions/bulk-encrypt.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 36db5b87079..fe946fbb064 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -14,19 +14,20 @@ import { } from "rxjs"; import { SemVer } from "semver"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service"; - +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../key-management/src/abstractions/key.service"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; +import { AccountService } from "../../auth/abstractions/account.service"; import { AutofillSettingsServiceAbstraction } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; import { View } from "../../models/view/view"; +import { BulkEncryptService } from "../../platform/abstractions/bulk-encrypt.service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; diff --git a/libs/common/src/vault/services/folder/folder-api.service.ts b/libs/common/src/vault/services/folder/folder-api.service.ts index 24831393668..9d434cc646d 100644 --- a/libs/common/src/vault/services/folder/folder-api.service.ts +++ b/libs/common/src/vault/services/folder/folder-api.service.ts @@ -1,6 +1,5 @@ -import { UserId } from "@bitwarden/common/types/guid"; - import { ApiService } from "../../../abstractions/api.service"; +import { UserId } from "../../../types/guid"; import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; import { FolderData } from "../../../vault/models/data/folder.data"; diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 612cd83d99b..cc3aa1946ca 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeEncString } from "../../../../spec"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; @@ -75,7 +77,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folders$(mockUserId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: makeEncString("ENC_STRING_1") }, { id: "2", name: makeEncString("ENC_STRING_2") }, ]); @@ -96,7 +98,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folderViews$(mockUserId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: "DEC" }, { id: "2", name: "DEC" }, { name: "No Folder" }, diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 3aac5374fcb..c21a92fd894 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -2,9 +2,10 @@ // @ts-strict-ignore import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge } from "rxjs"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - +import { EncryptService } from ".././../../platform/abstractions/encrypt.service"; +import { Utils } from ".././../../platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { I18nService } from "../../../platform/abstractions/i18n.service"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index 27d8acbd360..c28b60e28f8 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -1,5 +1,16 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"] + } + }, "include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts index c8e9d3d2fe3..ef7ba68b65c 100644 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ b/libs/components/src/a11y/a11y-grid.directive.ts @@ -86,6 +86,8 @@ export class A11yGridDirective implements AfterViewInit { }); this.getActiveCellContent().tabIndex = 0; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // eslint-disable-next-line no-console console.error("Unable to initialize grid"); diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index fb94e43b196..7ddb32da281 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -5,6 +5,8 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { delay, of } from "rxjs"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; import { ButtonModule } from "../button"; diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index c322f29b957..f3e5a5cb3f7 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -9,6 +9,8 @@ import { } from "@angular/forms"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { I18nService } from "@bitwarden/common/src/platform/abstractions/i18n.service"; import { BadgeModule } from "../badge"; diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index 5b07d0bbb96..a653d79f83f 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -18,6 +18,8 @@ import { import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { compareValues } from "../../../common/src/platform/misc/compare-values"; import { ButtonModule } from "../button"; import { IconButtonModule } from "../icon-button"; diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index ccd80d6fa75..6d8323e088e 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -1,4 +1,7 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { TextFieldModule } from "@angular/cdk/text-field"; +import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; import { AbstractControl, UntypedFormBuilder, @@ -10,7 +13,6 @@ import { } from "@angular/forms"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; -import { A11yTitleDirective } from "@bitwarden/angular/src/directives/a11y-title.directive"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { AsyncActionsModule } from "../async-actions"; @@ -29,6 +31,41 @@ import { I18nMockService } from "../utils/i18n-mock.service"; import { BitFormFieldComponent } from "./form-field.component"; import { FormFieldModule } from "./form-field.module"; +// TOOD: This solves a circular dependency between components and angular. +@Directive({ + selector: "[appA11yTitle]", +}) +export class A11yTitleDirective implements OnInit { + @Input() set appA11yTitle(title: string) { + this.title = title; + this.setAttributes(); + } + + private title: string; + private originalTitle: string | null; + private originalAriaLabel: string | null; + + constructor( + private el: ElementRef, + private renderer: Renderer2, + ) {} + + ngOnInit() { + this.originalTitle = this.el.nativeElement.getAttribute("title"); + this.originalAriaLabel = this.el.nativeElement.getAttribute("aria-label"); + this.setAttributes(); + } + + private setAttributes() { + if (this.originalTitle === null) { + this.renderer.setAttribute(this.el.nativeElement, "title", this.title); + } + if (this.originalAriaLabel === null) { + this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title); + } + } +} + export default { title: "Component Library/Form/Field", component: BitFormFieldComponent, diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 58d93ddd3a4..d615bfe0582 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -70,6 +70,8 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI setOpen(isOpen: boolean) { this.open = isOpen; this.openChange.emit(this.open); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.open && this.parentNavGroup?.setOpen(this.open); } diff --git a/libs/components/src/section/section-header.component.html b/libs/components/src/section/section-header.component.html index 3f96e22540f..f070cfeae02 100644 --- a/libs/components/src/section/section-header.component.html +++ b/libs/components/src/section/section-header.component.html @@ -2,7 +2,7 @@
-
+
diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index 02b49a3e915..7709506f050 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -12,7 +12,7 @@ import { TableDataSource, TableModule } from "../../../table"; imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], template: /*html*/ ` - + Id diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index 44080e29049..a90597c1710 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -196,4 +196,10 @@ export const VirtualScrollBlockingDialog: Story = { await userEvent.click(dialogButton); }, + parameters: { + chromatic: { + // TODO CL-524 fix flaky story (number of virtual scroll rows is inconsistent) + disableSnapshot: true, + }, + }, }; diff --git a/libs/components/src/toast/index.ts b/libs/components/src/toast/index.ts index f0b55402194..5afed111c0f 100644 --- a/libs/components/src/toast/index.ts +++ b/libs/components/src/toast/index.ts @@ -1,2 +1,3 @@ export * from "./toast.module"; export * from "./toast.service"; +export type { ToastVariant } from "./toast.component"; diff --git a/libs/components/tailwind.config.base.js b/libs/components/tailwind.config.base.js index 26616d07156..6e887030c34 100644 --- a/libs/components/tailwind.config.base.js +++ b/libs/components/tailwind.config.base.js @@ -11,6 +11,7 @@ module.exports = { content: [ "./src/**/*.{html,ts}", "../../libs/components/src/**/*.{html,ts}", + "../../libs/key-management/src/**/*.{html,ts}", "../../libs/auth/src/**/*.{html,ts}", ], safelist: [], diff --git a/libs/components/tsconfig.json b/libs/components/tsconfig.json index dabcecf78e9..71eef15fac4 100644 --- a/libs/components/tsconfig.json +++ b/libs/components/tsconfig.json @@ -20,7 +20,6 @@ "lib": ["es2020", "dom"], "paths": { "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/angular/*": ["../angular/src/*"], "@bitwarden/platform": ["../platform/src"] }, "plugins": [ diff --git a/libs/importer/jest.config.js b/libs/importer/jest.config.js index 68daba3d407..ab449dc7757 100644 --- a/libs/importer/jest.config.js +++ b/libs/importer/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../shared/jest.config.ts"); diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 0da8127369e..25172f850b7 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -395,6 +395,11 @@

{{ "data" | i18n }}

Open the FullClient, go to the Main Menu and select Export. Start the export passwords wizard and follow the instructions to export a CSV file.
+ + Log in to NordPass and open Settings → Scroll down to the Import and Export section + and select Export items → Enter your Master Password and select Continue. → Save + the CSV file on your device. + { function createCipher(options: Partial = {}) { const cipher = new CipherView(); - cipher.name; + cipher.name = options.name; cipher.type = options.type; cipher.folderId = options.folderId; cipher.collectionIds = options.collectionIds; diff --git a/libs/importer/tsconfig.json b/libs/importer/tsconfig.json index 6004a56fb55..2235cccb5c7 100644 --- a/libs/importer/tsconfig.json +++ b/libs/importer/tsconfig.json @@ -1,5 +1,21 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/key-management/jest.config.js b/libs/key-management/jest.config.js index e20d02303d9..ad8023e906b 100644 --- a/libs/key-management/jest.config.js +++ b/libs/key-management/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index f5ab624bb22..da59c2986b2 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -5,13 +5,29 @@ import { Observable } from "rxjs"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; import { KdfConfig } from "@bitwarden/key-management"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-provider-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderResponse } from "../../../common/src/admin-console/models/response/profile-provider.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeySuffixOptions, HashPurpose } from "../../../common/src/platform/enums"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedString, EncString } from "../../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SymmetricCryptoKey } from "../../../common/src/platform/models/domain/symmetric-crypto-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationId, UserId } from "../../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserKey, MasterKey, diff --git a/libs/key-management/src/angular/lock/components/lock.component.ts b/libs/key-management/src/angular/lock/components/lock.component.ts index e9c7d0d6073..6c912b0eaae 100644 --- a/libs/key-management/src/angular/lock/components/lock.component.ts +++ b/libs/key-management/src/angular/lock/components/lock.component.ts @@ -70,6 +70,9 @@ const clientTypeToSuccessRouteRecord: Partial> = { [ClientType.Browser]: "/tabs/current", }; +/// The minimum amount of time to wait after a process reload for a biometrics auto prompt to be possible +/// Fixes safari autoprompt behavior +const AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY = 5000; @Component({ selector: "bit-lock", templateUrl: "lock.component.html", @@ -304,7 +307,15 @@ export class LockComponent implements OnInit, OnDestroy { (await this.biometricService.getShouldAutopromptNow()) ) { await this.biometricService.setShouldAutopromptNow(false); - await this.unlockViaBiometrics(); + + const lastProcessReload = await this.biometricStateService.getLastProcessReload(); + if ( + lastProcessReload == null || + isNaN(lastProcessReload.getTime()) || + Date.now() - lastProcessReload.getTime() > AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY + ) { + await this.unlockViaBiometrics(); + } } } } diff --git a/libs/key-management/src/biometrics/biometric-state.service.ts b/libs/key-management/src/biometrics/biometric-state.service.ts index 138e2589b1c..ae6e52ce632 100644 --- a/libs/key-management/src/biometrics/biometric-state.service.ts +++ b/libs/key-management/src/biometrics/biometric-state.service.ts @@ -2,8 +2,14 @@ // @ts-strict-ignore import { Observable, firstValueFrom, map, combineLatest } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedString, EncString } from "../../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ActiveUserState, GlobalState, StateProvider } from "../../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "../../../common/src/types/guid"; import { @@ -14,6 +20,7 @@ import { PROMPT_AUTOMATICALLY, PROMPT_CANCELLED, FINGERPRINT_VALIDATED, + LAST_PROCESS_RELOAD, } from "./biometric.state"; export abstract class BiometricStateService { @@ -106,6 +113,10 @@ export abstract class BiometricStateService { */ abstract setFingerprintValidated(validated: boolean): Promise; + abstract updateLastProcessReload(): Promise; + + abstract getLastProcessReload(): Promise; + abstract logout(userId: UserId): Promise; } @@ -117,6 +128,7 @@ export class DefaultBiometricStateService implements BiometricStateService { private promptCancelledState: GlobalState>; private promptAutomaticallyState: ActiveUserState; private fingerprintValidatedState: GlobalState; + private lastProcessReloadState: GlobalState; biometricUnlockEnabled$: Observable; encryptedClientKeyHalf$: Observable; requirePasswordOnStart$: Observable; @@ -124,6 +136,7 @@ export class DefaultBiometricStateService implements BiometricStateService { promptCancelled$: Observable; promptAutomatically$: Observable; fingerprintValidated$: Observable; + lastProcessReload$: Observable; constructor(private stateProvider: StateProvider) { this.biometricUnlockEnabledState = this.stateProvider.getActive(BIOMETRIC_UNLOCK_ENABLED); @@ -159,6 +172,9 @@ export class DefaultBiometricStateService implements BiometricStateService { this.fingerprintValidatedState = this.stateProvider.getGlobal(FINGERPRINT_VALIDATED); this.fingerprintValidated$ = this.fingerprintValidatedState.state$.pipe(map(Boolean)); + + this.lastProcessReloadState = this.stateProvider.getGlobal(LAST_PROCESS_RELOAD); + this.lastProcessReload$ = this.lastProcessReloadState.state$; } async setBiometricUnlockEnabled(enabled: boolean): Promise { @@ -270,6 +286,14 @@ export class DefaultBiometricStateService implements BiometricStateService { async setFingerprintValidated(validated: boolean): Promise { await this.fingerprintValidatedState.update(() => validated); } + + async updateLastProcessReload(): Promise { + await this.lastProcessReloadState.update(() => new Date()); + } + + async getLastProcessReload(): Promise { + return await firstValueFrom(this.lastProcessReload$); + } } function encryptedClientKeyHalfToEncString( diff --git a/libs/key-management/src/biometrics/biometric.state.ts b/libs/key-management/src/biometrics/biometric.state.ts index f88bd1da581..e5ea4fbae3b 100644 --- a/libs/key-management/src/biometrics/biometric.state.ts +++ b/libs/key-management/src/biometrics/biometric.state.ts @@ -1,9 +1,15 @@ +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedString } from "../../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyDefinition, BIOMETRIC_SETTINGS_DISK, UserKeyDefinition, } from "../../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "../../../common/src/types/guid"; /** @@ -95,3 +101,14 @@ export const FINGERPRINT_VALIDATED = new KeyDefinition( deserializer: (obj) => obj, }, ); + +/** + * Last process reload time + */ +export const LAST_PROCESS_RELOAD = new KeyDefinition( + BIOMETRIC_SETTINGS_DISK, + "lastProcessReload", + { + deserializer: (obj) => new Date(obj), + }, +); diff --git a/libs/key-management/src/kdf-config.service.spec.ts b/libs/key-management/src/kdf-config.service.spec.ts index c9912930e2c..76a13fe10fc 100644 --- a/libs/key-management/src/kdf-config.service.spec.ts +++ b/libs/key-management/src/kdf-config.service.spec.ts @@ -3,8 +3,12 @@ import { FakeStateProvider, mockAccountServiceWith, } from "@bitwarden/common/spec"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Utils } from "../../common/src/platform/misc/utils"; import { DefaultKdfConfigService } from "./kdf-config.service"; diff --git a/libs/key-management/src/kdf-config.service.ts b/libs/key-management/src/kdf-config.service.ts index 884a94fb95f..f0c964c5d2e 100644 --- a/libs/key-management/src/kdf-config.service.ts +++ b/libs/key-management/src/kdf-config.service.ts @@ -2,8 +2,12 @@ // @ts-strict-ignore import { firstValueFrom, Observable } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserId } from "@bitwarden/common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KDF_CONFIG_DISK, StateProvider, UserKeyDefinition } from "../../common/src/platform/state"; import { KdfConfigService } from "./abstractions/kdf-config.service"; diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 142a8bbeb86..62cdce230ea 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -3,6 +3,8 @@ import { bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs" import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../auth/src/common/abstractions"; import { awaitAsync, @@ -13,29 +15,69 @@ import { import { FakeAccountService, mockAccountServiceWith } from "../../common/spec/fake-account-service"; import { FakeActiveUserState, FakeSingleUserState } from "../../common/spec/fake-state"; import { FakeStateProvider } from "../../common/spec/fake-state-provider"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { FakeMasterPasswordService } from "../../common/src/auth/services/master-password/fake-master-password.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptService } from "../../common/src/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogService } from "../../common/src/platform/abstractions/log.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { StateService } from "../../common/src/platform/abstractions/state.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Encrypted } from "../../common/src/platform/interfaces/encrypted"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Utils } from "../../common/src/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PRIVATE_KEY, USER_EVER_HAD_USER_KEY, USER_KEY, } from "../../common/src/platform/services/key-state/user-key.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserKeyDefinition } from "../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CsprngArray } from "../../common/src/types/csprng"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationId, UserId } from "../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserKey, MasterKey } from "../../common/src/types/key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type"; import { KdfConfigService } from "./abstractions/kdf-config.service"; @@ -462,6 +504,7 @@ describe("keyService", () => { expect(encryptService.decryptToBytes).toHaveBeenCalledWith( fakeEncryptedUserPrivateKey, userKey, + "Content: Encrypted Private Key", ); expect(userPrivateKey).toBe(fakeDecryptedUserPrivateKey); diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index fba0ce60b74..98474b4babf 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -12,37 +12,93 @@ import { switchMap, } from "rxjs"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "../../auth/src/common/abstractions"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptedOrganizationKeyData } from "../../common/src/admin-console/models/data/encrypted-organization-key.data"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BaseEncryptedOrganizationKey } from "../../common/src/admin-console/models/domain/encrypted-organization-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileOrganizationResponse } from "../../common/src/admin-console/models/response/profile-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderOrganizationResponse } from "../../common/src/admin-console/models/response/profile-provider-organization.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ProfileProviderResponse } from "../../common/src/admin-console/models/response/profile-provider.response"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { AccountService } from "../../common/src/auth/abstractions/account.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { InternalMasterPasswordServiceAbstraction } from "../../common/src/auth/abstractions/master-password.service.abstraction"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncryptService } from "../../common/src/platform/abstractions/encrypt.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { LogService } from "../../common/src/platform/abstractions/log.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { StateService } from "../../common/src/platform/abstractions/state.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { KeySuffixOptions, HashPurpose } from "../../common/src/platform/enums"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { convertValues } from "../../common/src/platform/misc/convert-values"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Utils } from "../../common/src/platform/misc/utils"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EFFLongWordList } from "../../common/src/platform/misc/wordlist"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { USER_ENCRYPTED_PRIVATE_KEY, USER_EVER_HAD_USER_KEY, USER_KEY, } from "../../common/src/platform/services/key-state/user-key.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ActiveUserState, StateProvider } from "../../common/src/platform/state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { CsprngArray } from "../../common/src/types/csprng"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrganizationId, ProviderId, UserId } from "../../common/src/types/guid"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { OrgKey, UserKey, @@ -52,6 +108,8 @@ import { UserPrivateKey, UserPublicKey, } from "../../common/src/types/key"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type"; import { KdfConfigService } from "./abstractions/kdf-config.service"; @@ -382,7 +440,6 @@ export class DefaultKeyService implements KeyServiceAbstraction { key: org.key, }; }); - return encOrgKeyData; }); } @@ -626,6 +683,8 @@ export class DefaultKeyService implements KeyServiceAbstraction { // failed to decrypt return false; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return false; } @@ -891,6 +950,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { return (await this.encryptService.decryptToBytes( new EncString(encryptedPrivateKey), key, + "Content: Encrypted Private Key", )) as UserPrivateKey; } diff --git a/libs/key-management/src/models/kdf-config.ts b/libs/key-management/src/models/kdf-config.ts index 11431337a39..fe9c0de255c 100644 --- a/libs/key-management/src/models/kdf-config.ts +++ b/libs/key-management/src/models/kdf-config.ts @@ -1,5 +1,7 @@ import { Jsonify } from "type-fest"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { RangeWithDefault } from "../../../common/src/platform/misc/range-with-default"; import { KdfType } from "../enums/kdf-type.enum"; diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts index 77d7ebbb814..5312113d760 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts @@ -153,6 +153,56 @@ describe("regenerateIfNeeded", () => { expect(keyService.setPrivateKey).not.toHaveBeenCalled(); }); + it("should not regenerate when user symmetric key is unavailable", async () => { + const mockVerificationResponse: VerifyAsymmetricKeysResponse = { + privateKeyDecryptable: true, + validPrivateKey: false, + }; + setupVerificationResponse(mockVerificationResponse, sdkService); + keyService.userKey$.mockReturnValue(of(undefined as unknown as UserKey)); + + await sut.regenerateIfNeeded(userId); + + expect( + userAsymmetricKeysRegenerationApiService.regenerateUserAsymmetricKeys, + ).not.toHaveBeenCalled(); + expect(keyService.setPrivateKey).not.toHaveBeenCalled(); + }); + + it("should not regenerate when user's encrypted private key is unavailable", async () => { + const mockVerificationResponse: VerifyAsymmetricKeysResponse = { + privateKeyDecryptable: true, + validPrivateKey: false, + }; + setupVerificationResponse(mockVerificationResponse, sdkService); + keyService.userEncryptedPrivateKey$.mockReturnValue( + of(undefined as unknown as EncryptedString), + ); + + await sut.regenerateIfNeeded(userId); + + expect( + userAsymmetricKeysRegenerationApiService.regenerateUserAsymmetricKeys, + ).not.toHaveBeenCalled(); + expect(keyService.setPrivateKey).not.toHaveBeenCalled(); + }); + + it("should not regenerate when user's public key is unavailable", async () => { + const mockVerificationResponse: VerifyAsymmetricKeysResponse = { + privateKeyDecryptable: true, + validPrivateKey: false, + }; + setupVerificationResponse(mockVerificationResponse, sdkService); + apiService.getUserPublicKey.mockResolvedValue(undefined as any); + + await sut.regenerateIfNeeded(userId); + + expect( + userAsymmetricKeysRegenerationApiService.regenerateUserAsymmetricKeys, + ).not.toHaveBeenCalled(); + expect(keyService.setPrivateKey).not.toHaveBeenCalled(); + }); + it("should regenerate when private key is decryptable and invalid", async () => { const mockVerificationResponse: VerifyAsymmetricKeysResponse = { privateKeyDecryptable: true, diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts index ffaa3a82608..3110ebad637 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts @@ -49,14 +49,31 @@ export class DefaultUserAsymmetricKeysRegenerationService } private async shouldRegenerate(userId: UserId): Promise { - const [userKey, userKeyEncryptedPrivateKey, publicKeyResponse] = await firstValueFrom( + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + + // For SSO logins from untrusted devices, the userKey will not be available, and the private key regeneration process should be skipped. + // In such cases, regeneration will occur on the following device login flow. + if (!userKey) { + this.logService.info( + "[UserAsymmetricKeyRegeneration] User symmetric key unavailable, skipping regeneration for the user.", + ); + return false; + } + + const [userKeyEncryptedPrivateKey, publicKeyResponse] = await firstValueFrom( combineLatest([ - this.keyService.userKey$(userId), this.keyService.userEncryptedPrivateKey$(userId), this.apiService.getUserPublicKey(userId), ]), ); + if (!userKeyEncryptedPrivateKey || !publicKeyResponse) { + this.logService.warning( + "[UserAsymmetricKeyRegeneration] User's asymmetric key initialization data is unavailable, skipping regeneration.", + ); + return false; + } + const verificationResponse = await firstValueFrom( this.sdkService.client$.pipe( map((sdk) => { diff --git a/libs/key-management/tsconfig.json b/libs/key-management/tsconfig.json index 6004a56fb55..8279f14c786 100644 --- a/libs/key-management/tsconfig.json +++ b/libs/key-management/tsconfig.json @@ -1,5 +1,21 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/angular": ["../auth/src/angular"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/platform": ["../platform/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/node/jest.config.js b/libs/node/jest.config.js index dc98adf8ddd..1a33ad39406 100644 --- a/libs/node/jest.config.js +++ b/libs/node/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../shared/jest.config.ts"); diff --git a/libs/node/src/services/node-crypto-function.service.spec.ts b/libs/node/src/services/node-crypto-function.service.spec.ts index 61200b92855..3256d85110f 100644 --- a/libs/node/src/services/node-crypto-function.service.spec.ts +++ b/libs/node/src/services/node-crypto-function.service.spec.ts @@ -1,5 +1,5 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters"; +import { EcbDecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { NodeCryptoFunctionService } from "./node-crypto-function.service"; @@ -193,8 +193,8 @@ describe("NodeCrypto Function Service", () => { const iv = Utils.fromBufferToB64(makeStaticByteArray(16)); const symKey = new SymmetricCryptoKey(makeStaticByteArray(32)); const data = "ByUF8vhyX4ddU9gcooznwA=="; - const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); - const decValue = await nodeCryptoFunctionService.aesDecryptFast(params, "cbc"); + const parameters = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); + const decValue = await nodeCryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -202,10 +202,11 @@ describe("NodeCrypto Function Service", () => { describe("aesDecryptFast ECB mode", () => { it("should successfully decrypt data", async () => { const nodeCryptoFunctionService = new NodeCryptoFunctionService(); - const params = new DecryptParameters(); - params.encKey = makeStaticByteArray(32); - params.data = Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw=="); - const decValue = await nodeCryptoFunctionService.aesDecryptFast(params, "ecb"); + const parameters: EcbDecryptParameters = { + encKey: makeStaticByteArray(32), + data: Utils.fromB64ToArray("z5q2XSxYCdQFdI+qK2yLlw=="), + }; + const decValue = await nodeCryptoFunctionService.aesDecryptFast({ mode: "ecb", parameters }); expect(decValue).toBe("EncryptMe!"); }); }); @@ -219,6 +220,15 @@ describe("NodeCrypto Function Service", () => { const decValue = await nodeCryptoFunctionService.aesDecrypt(data, iv, key, "cbc"); expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); }); + + it("throws if IV is not provided", async () => { + const nodeCryptoFunctionService = new NodeCryptoFunctionService(); + const key = makeStaticByteArray(32); + const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA=="); + await expect( + async () => await nodeCryptoFunctionService.aesDecrypt(data, null, key, "cbc"), + ).rejects.toThrow("Invalid initialization vector"); + }); }); describe("aesDecrypt ECB mode", () => { @@ -454,7 +464,7 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string, fast = f const cryptoFunctionService = new NodeCryptoFunctionService(); const value = Utils.fromUtf8ToArray("SignMe!!"); const key = Utils.fromUtf8ToArray("secretkey"); - let computedMac: ArrayBuffer = null; + let computedMac: ArrayBuffer; if (fast) { computedMac = await cryptoFunctionService.hmacFast(value, key, algorithm); } else { diff --git a/libs/node/src/services/node-crypto-function.service.ts b/libs/node/src/services/node-crypto-function.service.ts index 74d6de2e34c..c06f18023b4 100644 --- a/libs/node/src/services/node-crypto-function.service.ts +++ b/libs/node/src/services/node-crypto-function.service.ts @@ -1,12 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import * as crypto from "crypto"; import * as forge from "node-forge"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters"; +import { + CbcDecryptParameters, + EcbDecryptParameters, +} from "@bitwarden/common/platform/models/domain/decrypt-parameters"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "@bitwarden/common/types/csprng"; @@ -168,10 +169,10 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { aesDecryptFastParameters( data: string, iv: string, - mac: string, + mac: string | null, key: SymmetricCryptoKey, - ): DecryptParameters { - const p = new DecryptParameters(); + ): CbcDecryptParameters { + const p = {} as CbcDecryptParameters; p.encKey = key.encKey; p.data = Utils.fromB64ToArray(data); p.iv = Utils.fromB64ToArray(iv); @@ -191,22 +192,25 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { return p; } - async aesDecryptFast( - parameters: DecryptParameters, - mode: "cbc" | "ecb", - ): Promise { - const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey, mode); + async aesDecryptFast({ + mode, + parameters, + }: + | { mode: "cbc"; parameters: CbcDecryptParameters } + | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise { + const iv = mode === "cbc" ? parameters.iv : null; + const decBuf = await this.aesDecrypt(parameters.data, iv, parameters.encKey, mode); return Utils.fromBufferToUtf8(decBuf); } aesDecrypt( data: Uint8Array, - iv: Uint8Array, + iv: Uint8Array | null, key: Uint8Array, mode: "cbc" | "ecb", ): Promise { const nodeData = this.toNodeBuffer(data); - const nodeIv = mode === "ecb" ? null : this.toNodeBuffer(iv); + const nodeIv = this.toNodeBufferOrNull(iv); const nodeKey = this.toNodeBuffer(key); const decipher = crypto.createDecipheriv(this.toNodeCryptoAesMode(mode), nodeKey, nodeIv); const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]); @@ -311,6 +315,13 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { return Buffer.from(value); } + private toNodeBufferOrNull(value: Uint8Array | null): Buffer | null { + if (value == null) { + return null; + } + return this.toNodeBuffer(value); + } + private toUint8Buffer(value: Buffer | string | Uint8Array): Uint8Array { let buf: Uint8Array; if (typeof value === "string") { diff --git a/libs/node/tsconfig.json b/libs/node/tsconfig.json index 6004a56fb55..3d22cb2ec51 100644 --- a/libs/node/tsconfig.json +++ b/libs/node/tsconfig.json @@ -1,5 +1,13 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/key-management": ["../key-management/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/platform/jest.config.js b/libs/platform/jest.config.js index 4649b293a7a..063fb847d8f 100644 --- a/libs/platform/jest.config.js +++ b/libs/platform/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/platform/tsconfig.json b/libs/platform/tsconfig.json index 6004a56fb55..eaa021247d8 100644 --- a/libs/platform/tsconfig.json +++ b/libs/platform/tsconfig.json @@ -1,5 +1,8 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "paths": {} + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/shared/jest.config.angular.js b/libs/shared/jest.config.angular.js index de703e27220..311318e46a4 100644 --- a/libs/shared/jest.config.angular.js +++ b/libs/shared/jest.config.angular.js @@ -1,5 +1,5 @@ /* eslint-env node */ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const { defaultTransformerOptions } = require("jest-preset-angular/presets"); /** @type {import('jest').Config} */ diff --git a/libs/shared/tsconfig.libs.json b/libs/shared/tsconfig.spec.json similarity index 100% rename from libs/shared/tsconfig.libs.json rename to libs/shared/tsconfig.spec.json diff --git a/libs/tools/card/jest.config.js b/libs/tools/card/jest.config.js index b68bda8d5ca..952e9ce0e2e 100644 --- a/libs/tools/card/jest.config.js +++ b/libs/tools/card/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/card/tsconfig.json b/libs/tools/card/tsconfig.json index 52eed3035a6..9c910521cc6 100644 --- a/libs/tools/card/tsconfig.json +++ b/libs/tools/card/tsconfig.json @@ -1,5 +1,16 @@ { - "extends": "../../shared/tsconfig.libs", + "extends": "../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../angular/src/*"], + "@bitwarden/auth/common": ["../../auth/src/common"], + "@bitwarden/common/*": ["../../common/src/*"], + "@bitwarden/components": ["../../components/src"], + "@bitwarden/key-management": ["../../key-management/src"], + "@bitwarden/platform": ["../../platform/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/export/vault-export/vault-export-core/jest.config.js b/libs/tools/export/vault-export/vault-export-core/jest.config.js index 955b8e7763c..0a78a9855dc 100644 --- a/libs/tools/export/vault-export/vault-export-core/jest.config.js +++ b/libs/tools/export/vault-export/vault-export-core/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/export/vault-export/vault-export-core/tsconfig.json b/libs/tools/export/vault-export/vault-export-core/tsconfig.json index 5cb90260371..7652a271044 100644 --- a/libs/tools/export/vault-export/vault-export-core/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-core/tsconfig.json @@ -1,5 +1,13 @@ { - "extends": "../../../../shared/tsconfig.libs", + "extends": "../../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], + "@bitwarden/auth/common": ["../../../../auth/src/common"], + "@bitwarden/common/*": ["../../../../common/src/*"], + "@bitwarden/key-management": ["../../../../key-management/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/export/vault-export/vault-export-ui/jest.config.js b/libs/tools/export/vault-export/vault-export-ui/jest.config.js index 955b8e7763c..0a78a9855dc 100644 --- a/libs/tools/export/vault-export/vault-export-ui/jest.config.js +++ b/libs/tools/export/vault-export/vault-export-ui/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.libs"); +const { compilerOptions } = require("../../../../shared/tsconfig.spec"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json index 5cb90260371..8c8a04d4b62 100644 --- a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json @@ -1,5 +1,24 @@ { - "extends": "../../../../shared/tsconfig.libs", + "extends": "../../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../../../angular/src/*"], + "@bitwarden/auth/angular": ["../../../../auth/src/angular"], + "@bitwarden/auth/common": ["../../../../auth/src/common"], + "@bitwarden/common/*": ["../../../../common/src/*"], + "@bitwarden/components": ["../../../../components/src"], + "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], + "@bitwarden/generator-history": ["../../../../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../../../../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../../../../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../../../key-management/src"], + "@bitwarden/platform": ["../../../../platform/src"], + "@bitwarden/vault-export-core": [ + "../../../../tools/export/vault-export/vault-export-core/src" + ] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/components/jest.config.js b/libs/tools/generator/components/jest.config.js index c34d909fe8d..bf5e465f398 100644 --- a/libs/tools/generator/components/jest.config.js +++ b/libs/tools/generator/components/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.libs.json"); +const { compilerOptions } = require("../../../shared/tsconfig.spec.json"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/tools/generator/components/src/catchall-settings.component.html b/libs/tools/generator/components/src/catchall-settings.component.html index 61037c91a73..4afa145c055 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.html +++ b/libs/tools/generator/components/src/catchall-settings.component.html @@ -1,4 +1,4 @@ -
+ {{ "domainName" | i18n }} {{ "clearHistory" | i18n }} + + diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts index cec818b1cd6..7bcffd92399 100644 --- a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -34,6 +35,7 @@ export class CredentialGeneratorHistoryDialogComponent { private accountService: AccountService, private history: GeneratorHistoryService, private dialogService: DialogService, + private dialogRef: DialogRef, ) { this.accountService.activeAccount$ .pipe( @@ -52,7 +54,13 @@ export class CredentialGeneratorHistoryDialogComponent { .subscribe(this.hasHistory$); } - clear = async () => { + /** closes the dialog */ + protected close() { + this.dialogRef.close(); + } + + /** Launches clear history flow */ + protected async clear() { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "clearGeneratorHistoryTitle" }, content: { key: "cleargGeneratorHistoryDescription" }, @@ -64,5 +72,5 @@ export class CredentialGeneratorHistoryDialogComponent { if (confirmed) { await this.history.clear(await firstValueFrom(this.userId$)); } - }; + } } diff --git a/libs/tools/generator/components/src/credential-generator.component.html b/libs/tools/generator/components/src/credential-generator.component.html index ce86abe80ae..6f27c7020db 100644 --- a/libs/tools/generator/components/src/credential-generator.component.html +++ b/libs/tools/generator/components/src/credential-generator.component.html @@ -56,7 +56,7 @@

{{ "options" | i18n }}

- + {{ "type" | i18n }} {{ "options" | i18n }} }} -
+ {{ "service" | i18n }} + {{ "forwarderDomainName" | i18n }}
{{ "options" | i18n }}
- +
diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index 7e230d8bfd6..85363412ffa 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -19,8 +19,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { Option } from "@bitwarden/components/src/select/option"; +import { ToastService, Option } from "@bitwarden/components"; import { CredentialGeneratorService, Generators, @@ -94,6 +93,10 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy { @Output() readonly onGenerated = new EventEmitter(); + /** emits algorithm info when the selected algorithm changes */ + @Output() + readonly onAlgorithm = new EventEmitter(); + async ngOnInit() { if (this.userId) { this.userId$.next(this.userId); @@ -186,6 +189,7 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy { // template bindings refresh immediately this.zone.run(() => { this.algorithm$.next(algorithm); + this.onAlgorithm.next(algorithm); }); }); diff --git a/libs/tools/generator/components/src/password-settings.component.html b/libs/tools/generator/components/src/password-settings.component.html index 9f8e00921fb..5e4d1079725 100644 --- a/libs/tools/generator/components/src/password-settings.component.html +++ b/libs/tools/generator/components/src/password-settings.component.html @@ -2,7 +2,7 @@

{{ "options" | i18n }}

- +
diff --git a/libs/tools/generator/components/src/subaddress-settings.component.html b/libs/tools/generator/components/src/subaddress-settings.component.html index 1dfb5e3460d..b7f71b12b2a 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.html +++ b/libs/tools/generator/components/src/subaddress-settings.component.html @@ -1,4 +1,4 @@ - + {{ "email" | i18n }} {{ "options" | i18n }}
- + {{ "type" | i18n }} {{ "options" | i18n }} }} -
+ {{ "service" | i18n }} (); + /** emits algorithm info when the selected algorithm changes */ + @Output() + readonly onAlgorithm = new EventEmitter(); + /** Removes bottom margin from internal elements */ @Input({ transform: coerceBooleanProperty }) disableMargin = false; @@ -248,6 +251,7 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy { // template bindings refresh immediately this.zone.run(() => { this.algorithm$.next(algorithm); + this.onAlgorithm.next(algorithm); }); }); diff --git a/libs/tools/generator/components/src/username-settings.component.html b/libs/tools/generator/components/src/username-settings.component.html index 649cd052e7b..fd8b32e2b3e 100644 --- a/libs/tools/generator/components/src/username-settings.component.html +++ b/libs/tools/generator/components/src/username-settings.component.html @@ -1,4 +1,4 @@ - + = { @@ -83,6 +84,7 @@ const SomeConfiguration: CredentialGeneratorConfiguration {{ send.name }} diff --git a/libs/tools/send/send-ui/tsconfig.json b/libs/tools/send/send-ui/tsconfig.json index c52bfd7b0df..671154e0a04 100644 --- a/libs/tools/send/send-ui/tsconfig.json +++ b/libs/tools/send/send-ui/tsconfig.json @@ -1,5 +1,21 @@ { - "extends": "../../../shared/tsconfig.libs", + "extends": "../../../shared/tsconfig", + "compilerOptions": { + "paths": { + "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], + "@bitwarden/angular/*": ["../../../angular/src/*"], + "@bitwarden/auth/common": ["../../../auth/src/common"], + "@bitwarden/common/*": ["../../../common/src/*"], + "@bitwarden/components": ["../../../components/src"], + "@bitwarden/generator-components": ["../../../tools/generator/components/src"], + "@bitwarden/generator-core": ["../../../tools/generator/core/src"], + "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../../../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../../../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../../../key-management/src"], + "@bitwarden/platform": ["../../../platform/src"] + } + }, "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/vault/jest.config.js b/libs/vault/jest.config.js index e960eed9f1a..e33c115e8dd 100644 --- a/libs/vault/jest.config.js +++ b/libs/vault/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.libs"); +const { compilerOptions } = require("../shared/tsconfig.spec"); const sharedConfig = require("../../libs/shared/jest.config.angular"); diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 72c4acb23cd..f7146776b73 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -31,7 +31,8 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; // FIXME: remove `/apps` import from `/libs` -// eslint-disable-next-line import/no-restricted-paths +// FIXME: remove `src` and fix import +// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests"; import { CipherFormService } from "./abstractions/cipher-form.service"; diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html index 445908679c3..16bccebb939 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.html @@ -2,9 +2,11 @@ *ngIf="type === 'password'" [disableMargin]="disableMargin" (onGenerated)="onCredentialGenerated($event)" + (onAlgorithm)="algorithm($event)" > diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts index 1a2bac2ce50..fdde5e15d91 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts @@ -5,7 +5,7 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { GeneratorModule } from "@bitwarden/generator-components"; -import { GeneratedCredential } from "@bitwarden/generator-core"; +import { AlgorithmInfo, GeneratedCredential } from "@bitwarden/generator-core"; /** * Renders a password or username generator UI and emits the most recently generated value. @@ -18,6 +18,9 @@ import { GeneratedCredential } from "@bitwarden/generator-core"; imports: [CommonModule, GeneratorModule], }) export class CipherFormGeneratorComponent { + @Input() + algorithm: (selected: AlgorithmInfo) => void; + /** * The type of generator form to show. */ diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts index f63a974d487..62d171b8436 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts @@ -38,6 +38,8 @@ describe("AddEditCustomFieldDialogComponent", () => { fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent); component = fixture.componentInstance; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions fixture.detectChanges; }); diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts index 0c0fa1b4184..a5e57d2858f 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.spec.ts @@ -19,6 +19,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; import { DialogService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "../../../../../components/src/form-field/password-input-toggle.directive"; import { CipherFormConfig } from "../../abstractions/cipher-form-config.service"; import { CipherFormContainer } from "../../cipher-form-container"; diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts index 182427f7f42..69cfd419742 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts @@ -14,6 +14,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "@bitwarden/components/src/form-field/password-input-toggle.directive"; import { CipherFormGenerationService } from "../../abstractions/cipher-form-generation.service"; diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 8503604bf7c..b4a0d4841f8 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -116,7 +116,7 @@

{{ "loginCredentials" | i18n }}

{{ "verificationCodeTotp" | i18n }} {{ "loginCredentials" | i18n }} id="totp" readonly bitInput - [type]="!(isPremium$ | async) ? 'password' : 'text'" + [type]="!(allowTotpGeneration$ | async) ? 'password' : 'text'" [value]="totpCodeCopyObj?.totpCodeFormatted || '*** ***'" aria-readonly="true" data-testid="login-totp" class="tw-font-mono" />
{{ "loginCredentials" | i18n }} showToast [appA11yTitle]="'copyVerificationCode' | i18n" data-testid="copy-totp" - [disabled]="!(isPremium$ | async)" + [disabled]="!(allowTotpGeneration$ | async)" class="disabled:tw-cursor-default" > diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts index 9c09028650e..9bb96d1accd 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts @@ -18,7 +18,11 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { BitFormFieldComponent, ToastService } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitPasswordInputToggleDirective } from "@bitwarden/components/src/form-field/password-input-toggle.directive"; import { LoginCredentialsViewComponent } from "./login-credentials-view.component"; diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index b24fcdfa1fd..281e187f78b 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -2,7 +2,15 @@ // @ts-strict-ignore import { CommonModule, DatePipe } from "@angular/common"; import { Component, inject, Input } from "@angular/core"; -import { Observable, switchMap } from "rxjs"; +import { + BehaviorSubject, + combineLatest, + filter, + map, + Observable, + shareReplay, + switchMap, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -12,15 +20,17 @@ import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { + BadgeModule, + ColorPasswordModule, FormFieldModule, + IconButtonModule, SectionComponent, SectionHeaderComponent, TypographyModule, - IconButtonModule, - BadgeModule, - ColorPasswordModule, } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { PremiumUpgradePromptService } from "../../../../../libs/common/src/vault/abstractions/premium-upgrade-prompt.service"; import { BitTotpCountdownComponent } from "../../components/totp-countdown/totp-countdown.component"; import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; @@ -49,13 +59,31 @@ type TotpCodeValues = { ], }) export class LoginCredentialsViewComponent { - @Input() cipher: CipherView; + @Input() + get cipher(): CipherView { + return this._cipher$.value; + } + set cipher(value: CipherView) { + this._cipher$.next(value); + } + private _cipher$ = new BehaviorSubject(null); - isPremium$: Observable = this.accountService.activeAccount$.pipe( + private _userHasPremium$: Observable = this.accountService.activeAccount$.pipe( switchMap((account) => this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), ), ); + + allowTotpGeneration$: Observable = combineLatest([ + this._userHasPremium$, + this._cipher$.pipe(filter((c) => c != null)), + ]).pipe( + map(([userHasPremium, cipher]) => { + // User premium status only applies to personal ciphers, organizationUseTotp applies to organization ciphers + return (userHasPremium && cipher.organizationId == null) || cipher.organizationUseTotp; + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); showPasswordCount: boolean = false; passwordRevealed: boolean = false; totpCodeCopyObj: TotpCodeValues; diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts index 4c84b2852a8..21f82234b2c 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.spec.ts @@ -8,6 +8,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; import { SectionHeaderComponent } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { BitInputDirective } from "../../../../components/src/input/input.directive"; import { ViewIdentitySectionsComponent } from "./view-identity-sections.component"; diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index b22ced3d899..a47cdc8d1d5 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -98,6 +98,8 @@ export class DownloadAttachmentComponent { fileName: this.attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts b/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts index 461cc734d76..fb90df4d6ac 100644 --- a/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts +++ b/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts @@ -10,6 +10,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ColorPasswordModule, ItemModule } from "@bitwarden/components"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component"; import { PasswordHistoryViewComponent } from "./password-history-view.component"; diff --git a/libs/vault/src/services/password-reprompt.service.spec.ts b/libs/vault/src/services/password-reprompt.service.spec.ts index 300b98215c2..7d0a834b2d6 100644 --- a/libs/vault/src/services/password-reprompt.service.spec.ts +++ b/libs/vault/src/services/password-reprompt.service.spec.ts @@ -1,5 +1,7 @@ import { MockProxy, mock } from "jest-mock-extended"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { UserVerificationService } from "@bitwarden/common/src/auth/abstractions/user-verification/user-verification.service.abstraction"; import { DialogService } from "@bitwarden/components"; diff --git a/libs/vault/tsconfig.json b/libs/vault/tsconfig.json index 6004a56fb55..8318212e81d 100644 --- a/libs/vault/tsconfig.json +++ b/libs/vault/tsconfig.json @@ -1,5 +1,23 @@ { - "extends": "../shared/tsconfig.libs", + "extends": "../shared/tsconfig", + "compilerOptions": { + "resolveJsonModule": true, + "paths": { + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/angular/*": ["../angular/src/*"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/common/*": ["../common/src/*"], + "@bitwarden/components": ["../components/src"], + "@bitwarden/generator-components": ["../tools/generator/components/src"], + "@bitwarden/generator-core": ["../tools/generator/core/src"], + "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], + "@bitwarden/key-management": ["../key-management/src"], + "@bitwarden/platform": ["../platform/src"], + "@bitwarden/vault": ["../vault/src"] + } + }, "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/package-lock.json b/package-lock.json index 7994c8b0c2b..a8f82952517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "bootstrap": "4.6.0", "braintree-web-drop-in": "1.43.0", "buffer": "6.0.3", - "bufferutil": "4.0.8", + "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", @@ -70,7 +70,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.69", + "tldts": "6.1.71", "utf-8-validate": "6.0.5", "zone.js": "0.14.10", "zxcvbn": "4.4.2" @@ -83,7 +83,6 @@ "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", - "@angular/elements": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", @@ -113,7 +112,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -121,8 +120,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/eslint-plugin": "8.19.1", + "@typescript-eslint/parser": "8.19.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", @@ -130,11 +129,11 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.20.2", - "concurrently": "9.1.0", + "concurrently": "9.1.2", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", - "electron": "33.2.1", + "electron": "33.3.1", "electron-builder": "24.13.3", "electron-log": "5.2.4", "electron-reload": "2.0.0-alpha.1", @@ -152,7 +151,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", - "jest-extended": "4.0.2", + "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", @@ -166,7 +165,7 @@ "process": "0.11.10", "remark-gfm": "4.0.0", "rimraf": "6.0.1", - "sass": "1.81.0", + "sass": "1.83.1", "sass-loader": "16.0.4", "storybook": "8.4.7", "style-loader": "4.0.0", @@ -192,11 +191,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.1.0" + "version": "2025.1.1" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2025.1.0", + "version": "2025.1.1", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "3.0.2", @@ -223,7 +222,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.69", + "tldts": "6.1.71", "zxcvbn": "4.4.2" }, "bin": { @@ -232,7 +231,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.1.0", + "version": "2025.1.2", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -246,7 +245,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.1.0" + "version": "2025.1.1" }, "libs/admin-console": { "name": "@bitwarden/admin-console", @@ -2287,22 +2286,6 @@ "zone.js": "~0.14.10" } }, - "node_modules/@angular/elements": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-18.2.13.tgz", - "integrity": "sha512-yahRkXWgFolpWMeVsTIlWYwoq4Bsz6wrfS4b+TKHIZbTCyRUlJ5zBFecpYMwgmVuQDJZp+WkUWZV2Qg7kLJR5w==", - "dev": true, - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.13", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, "node_modules/@angular/forms": { "version": "18.2.13", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", @@ -9772,9 +9755,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10099,88 +10082,94 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", - "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", + "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/type-utils": "7.16.1", - "@typescript-eslint/utils": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/type-utils": "8.19.1", + "@typescript-eslint/utils": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", - "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", + "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/utils": "7.16.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/utils": "8.19.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", + "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/@typescript-eslint/experimental-utils": { @@ -10333,46 +10322,42 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz", - "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", + "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", + "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10380,13 +10365,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", + "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10394,32 +10379,43 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", + "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/@typescript-eslint/utils": { @@ -10530,23 +10526,36 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", + "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.19.1", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -12719,9 +12728,9 @@ "license": "MIT" }, "node_modules/bufferutil": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", - "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -13825,9 +13834,9 @@ } }, "node_modules/concurrently": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz", - "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", + "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15335,9 +15344,9 @@ } }, "node_modules/electron": { - "version": "33.2.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-33.2.1.tgz", - "integrity": "sha512-SG/nmSsK9Qg1p6wAW+ZfqU+AV8cmXMTIklUL18NnOKfZLlum4ZsDoVdmmmlL39ZmeCaq27dr7CgslRPahfoVJg==", + "version": "33.3.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.3.1.tgz", + "integrity": "sha512-Z7l2bVgpdKxHQMI4i0CirBX2n+iCYKOx5mbzNM3BpOyFELwlobEXKmzCmEnwP+3EcNeIhUQyIEBFQxN06QgdIw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -15576,9 +15585,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "20.17.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.8.tgz", - "integrity": "sha512-ahz2g6/oqbKalW9sPv6L2iRbhLnojxjYWspAqhjvqSWBgGebEJT5GvRmk0QXPj3sbC6rU0GTQjPLQkmR8CObvA==", + "version": "20.17.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz", + "integrity": "sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==", "dev": true, "license": "MIT", "dependencies": { @@ -20720,28 +20729,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-extended": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz", - "integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-diff": "^29.0.0", - "jest-get-type": "^29.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "jest": ">=27.2.5" - }, - "peerDependenciesMeta": { - "jest": { - "optional": true - } - } - }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -28777,9 +28764,9 @@ } }, "node_modules/sass": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.81.0.tgz", - "integrity": "sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==", + "version": "1.83.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.1.tgz", + "integrity": "sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==", "dev": true, "license": "MIT", "dependencies": { @@ -28839,9 +28826,9 @@ } }, "node_modules/sass/node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -30719,21 +30706,21 @@ } }, "node_modules/tldts": { - "version": "6.1.69", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.69.tgz", - "integrity": "sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==", + "version": "6.1.71", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.71.tgz", + "integrity": "sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.69" + "tldts-core": "^6.1.71" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.69", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.69.tgz", - "integrity": "sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==", + "version": "6.1.71", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.71.tgz", + "integrity": "sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==", "license": "MIT" }, "node_modules/tmp": { @@ -30875,6 +30862,7 @@ "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16" }, diff --git a/package.json b/package.json index 0a78d370d26..0af1445a8ae 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test:watch:all": "jest --watchAll", "test:types": "node ./scripts/test-types.js", "test:locales": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/test-locales.js", + "lint:dep-ownership": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/dep-ownership.js", "docs:json": "compodoc -p ./tsconfig.json -e json -d . --disableRoutesGraph", "storybook": "ng run components:storybook", "build-storybook": "ng run components:build-storybook", @@ -42,7 +43,6 @@ "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", - "@angular/elements": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", @@ -72,7 +72,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -80,8 +80,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/eslint-plugin": "8.19.1", + "@typescript-eslint/parser": "8.19.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", @@ -89,11 +89,11 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.20.2", - "concurrently": "9.1.0", + "concurrently": "9.1.2", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", - "electron": "33.2.1", + "electron": "33.3.1", "electron-builder": "24.13.3", "electron-log": "5.2.4", "electron-reload": "2.0.0-alpha.1", @@ -111,7 +111,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", - "jest-extended": "4.0.2", + "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", @@ -125,7 +125,7 @@ "process": "0.11.10", "remark-gfm": "4.0.0", "rimraf": "6.0.1", - "sass": "1.81.0", + "sass": "1.83.1", "sass-loader": "16.0.4", "storybook": "8.4.7", "style-loader": "4.0.0", @@ -168,7 +168,7 @@ "bootstrap": "4.6.0", "braintree-web-drop-in": "1.43.0", "buffer": "6.0.3", - "bufferutil": "4.0.8", + "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", @@ -200,7 +200,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.69", + "tldts": "6.1.71", "utf-8-validate": "6.0.5", "zone.js": "0.14.10", "zxcvbn": "4.4.2" diff --git a/scripts/dep-ownership.ts b/scripts/dep-ownership.ts new file mode 100644 index 00000000000..e574a3e9e96 --- /dev/null +++ b/scripts/dep-ownership.ts @@ -0,0 +1,31 @@ +/* eslint-disable no-console */ + +/// Ensure that all dependencies in package.json have an owner in the renovate.json file. + +import fs from "fs"; +import path from "path"; + +const renovateConfig = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "..", ".github", "renovate.json"), "utf8"), +); + +const packagesWithOwners = renovateConfig.packageRules + .flatMap((rule: any) => rule.matchPackageNames) + .filter((packageName: string) => packageName != null); + +const packageJson = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8"), +); +const dependencies = Object.keys(packageJson.dependencies).concat( + Object.keys(packageJson.devDependencies), +); + +const missingOwners = dependencies.filter((dep) => !packagesWithOwners.includes(dep)); + +if (missingOwners.length > 0) { + console.error("Missing owners for the following dependencies:"); + console.error(missingOwners.join("\n")); + process.exit(1); +} + +console.log("All dependencies have owners."); diff --git a/scripts/test-types.js b/scripts/test-types.js index 2155406cdf0..9534558af30 100644 --- a/scripts/test-types.js +++ b/scripts/test-types.js @@ -16,10 +16,15 @@ function getFiles(dir) { return results; } -const files = getFiles(path.join(__dirname, "..", "libs")).filter((file) => { - const name = path.basename(file); - return name === "tsconfig.spec.json"; -}); +const files = getFiles(path.join(__dirname, "..", "libs")) + .filter((file) => { + const name = path.basename(file); + return name === "tsconfig.spec.json"; + }) + .filter((path) => { + // Exclude shared since it's not actually a library + return !path.includes("libs/shared/"); + }); concurrently([ { diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 80f70e47b09..a69452389f5 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -14,6 +14,7 @@ "declarationDir": "dist/types", "outDir": "dist", "baseUrl": ".", + "allowJs": true, "paths": { "@bitwarden/admin-console": ["./libs/admin-console/src"], "@bitwarden/angular/*": ["./libs/angular/src/*"], @@ -35,6 +36,8 @@ "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/vault": ["./libs/vault/src"], + "@bitwarden/key-management": ["./libs/key-management/src"], + "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] }, "plugins": [